From b720d76bc9719f86f00e7da5acfb4e593c35cb1f Mon Sep 17 00:00:00 2001 From: Ben Jacobson Date: Wed, 16 Sep 2026 10:47:20 -0700 Subject: [PATCH 1/2] Add WSDL import and SOAP operation tools --- .changeset/wsdl-soap-integration.md | 8 + apps/cloud/executor.config.ts | 2 + apps/cloud/package.json | 1 + apps/desktop/package.json | 1 + apps/host-cloudflare/executor.config.ts | 2 + apps/host-cloudflare/package.json | 1 + apps/host-cloudflare/src/plugins.ts | 2 + apps/host-selfhost/executor.config.ts | 2 + apps/host-selfhost/package.json | 1 + apps/local/executor.config.ts | 2 + apps/local/package.json | 1 + bun.lock | 45 ++++ e2e/package.json | 1 + e2e/scenarios/wsdl-integration.test.ts | 74 ++++++ packages/plugins/wsdl/CHANGELOG.md | 1 + packages/plugins/wsdl/README.md | 58 +++++ packages/plugins/wsdl/package.json | 84 +++++++ packages/plugins/wsdl/src/api.ts | 28 +++ packages/plugins/wsdl/src/client.tsx | 121 +++++++++ packages/plugins/wsdl/src/codec.ts | 199 +++++++++++++++ packages/plugins/wsdl/src/contract.ts | 315 ++++++++++++++++++++++++ packages/plugins/wsdl/src/errors.ts | 4 + packages/plugins/wsdl/src/fixture.ts | 13 + packages/plugins/wsdl/src/group.ts | 22 ++ packages/plugins/wsdl/src/index.ts | 205 +++++++++++++++ packages/plugins/wsdl/src/invoke.ts | 72 ++++++ packages/plugins/wsdl/src/shared.ts | 14 ++ packages/plugins/wsdl/src/wsdl.test.ts | 202 +++++++++++++++ packages/plugins/wsdl/src/xml.ts | 113 +++++++++ packages/plugins/wsdl/tsconfig.json | 26 ++ packages/plugins/wsdl/tsup.config.ts | 14 ++ packages/plugins/wsdl/vitest.config.ts | 2 + 32 files changed, 1636 insertions(+) create mode 100644 .changeset/wsdl-soap-integration.md create mode 100644 e2e/scenarios/wsdl-integration.test.ts create mode 100644 packages/plugins/wsdl/CHANGELOG.md create mode 100644 packages/plugins/wsdl/README.md create mode 100644 packages/plugins/wsdl/package.json create mode 100644 packages/plugins/wsdl/src/api.ts create mode 100644 packages/plugins/wsdl/src/client.tsx create mode 100644 packages/plugins/wsdl/src/codec.ts create mode 100644 packages/plugins/wsdl/src/contract.ts create mode 100644 packages/plugins/wsdl/src/errors.ts create mode 100644 packages/plugins/wsdl/src/fixture.ts create mode 100644 packages/plugins/wsdl/src/group.ts create mode 100644 packages/plugins/wsdl/src/index.ts create mode 100644 packages/plugins/wsdl/src/invoke.ts create mode 100644 packages/plugins/wsdl/src/shared.ts create mode 100644 packages/plugins/wsdl/src/wsdl.test.ts create mode 100644 packages/plugins/wsdl/src/xml.ts create mode 100644 packages/plugins/wsdl/tsconfig.json create mode 100644 packages/plugins/wsdl/tsup.config.ts create mode 100644 packages/plugins/wsdl/vitest.config.ts diff --git a/.changeset/wsdl-soap-integration.md b/.changeset/wsdl-soap-integration.md new file mode 100644 index 0000000000..6ac625231f --- /dev/null +++ b/.changeset/wsdl-soap-integration.md @@ -0,0 +1,8 @@ +--- +"@executor-js/plugin-wsdl": minor +"@executor-js/local": minor +"@executor-js/desktop": minor +"@executor-js/host-selfhost": minor +--- + +Add WSDL integration import and SOAP operation tools for self-contained WSDL 1.1 document/literal contracts, with schema validation, SOAP fault handling, and HTTP authentication templates. diff --git a/apps/cloud/executor.config.ts b/apps/cloud/executor.config.ts index 71cd359999..2958475d4a 100644 --- a/apps/cloud/executor.config.ts +++ b/apps/cloud/executor.config.ts @@ -1,3 +1,4 @@ +import { wsdlHttpPlugin } from "@executor-js/plugin-wsdl/api"; import { defineExecutorConfig } from "@executor-js/sdk"; import { openApiHttpPlugin } from "@executor-js/plugin-openapi/api"; import { @@ -61,6 +62,7 @@ export default defineExecutorConfig({ dangerouslyAllowStdioMCP: false, }), graphqlHttpPlugin(), + wsdlHttpPlugin(), toolkitsPlugin({ activeToolkitSlug }), workosVaultPlugin({ credentials: workosCredentials ?? { apiKey: "", clientId: "" }, diff --git a/apps/cloud/package.json b/apps/cloud/package.json index 4feae6f1ac..780e5fc0eb 100644 --- a/apps/cloud/package.json +++ b/apps/cloud/package.json @@ -50,6 +50,7 @@ "@executor-js/plugin-openapi": "workspace:*", "@executor-js/plugin-toolkits": "workspace:*", "@executor-js/plugin-workos-vault": "workspace:*", + "@executor-js/plugin-wsdl": "workspace:*", "@executor-js/react": "workspace:*", "@executor-js/runtime-dynamic-worker": "workspace:*", "@executor-js/runtime-quickjs": "workspace:*", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 3d27a2ad8d..adef7fc04a 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -22,6 +22,7 @@ "typecheck:slow": "tsc --noEmit" }, "dependencies": { + "@executor-js/plugin-wsdl": "workspace:*", "@sentry/bun": "^10.57.0", "@sentry/electron": "7.19.0", "electron-log": "^5", diff --git a/apps/host-cloudflare/executor.config.ts b/apps/host-cloudflare/executor.config.ts index 5a47434b54..bbe4d4ff2e 100644 --- a/apps/host-cloudflare/executor.config.ts +++ b/apps/host-cloudflare/executor.config.ts @@ -1,3 +1,4 @@ +import { wsdlHttpPlugin } from "@executor-js/plugin-wsdl/api"; import { defineExecutorConfig } from "@executor-js/sdk"; import { openApiHttpPlugin } from "@executor-js/plugin-openapi/api"; import { @@ -31,6 +32,7 @@ export default defineExecutorConfig({ }), mcpHttpPlugin({ dangerouslyAllowStdioMCP: false }), graphqlHttpPlugin(), + wsdlHttpPlugin(), toolkitsPlugin({ activeToolkitSlug }), encryptedSecretsPlugin({ key: process.env.EXECUTOR_SECRET_KEY ?? "build-time-placeholder" }), ] as const, diff --git a/apps/host-cloudflare/package.json b/apps/host-cloudflare/package.json index b693834278..5a2ca5636e 100644 --- a/apps/host-cloudflare/package.json +++ b/apps/host-cloudflare/package.json @@ -30,6 +30,7 @@ "@executor-js/plugin-openapi": "workspace:*", "@executor-js/plugin-provider-service-split": "workspace:*", "@executor-js/plugin-toolkits": "workspace:*", + "@executor-js/plugin-wsdl": "workspace:*", "@executor-js/react": "workspace:*", "@executor-js/runtime-dynamic-worker": "workspace:*", "@executor-js/runtime-quickjs": "workspace:*", diff --git a/apps/host-cloudflare/src/plugins.ts b/apps/host-cloudflare/src/plugins.ts index ddd5f5e018..d119b5d5c2 100644 --- a/apps/host-cloudflare/src/plugins.ts +++ b/apps/host-cloudflare/src/plugins.ts @@ -1,3 +1,4 @@ +import { wsdlHttpPlugin } from "@executor-js/plugin-wsdl/api"; import { openApiHttpPlugin } from "@executor-js/plugin-openapi/api"; import { googleCatalog, @@ -34,6 +35,7 @@ export const makeCloudflarePlugins = ( }), mcpHttpPlugin({ dangerouslyAllowStdioMCP: false }), graphqlHttpPlugin(), + wsdlHttpPlugin(), toolkitsPlugin({ activeToolkitSlug: options.activeToolkitSlug }), encryptedSecretsPlugin({ key: secretKey }), ] as const; diff --git a/apps/host-selfhost/executor.config.ts b/apps/host-selfhost/executor.config.ts index c0826b9bcb..86d75b9be5 100644 --- a/apps/host-selfhost/executor.config.ts +++ b/apps/host-selfhost/executor.config.ts @@ -1,3 +1,4 @@ +import { wsdlHttpPlugin } from "@executor-js/plugin-wsdl/api"; import { defineExecutorConfig } from "@executor-js/sdk"; import { openApiHttpPlugin } from "@executor-js/plugin-openapi/api"; import { @@ -43,6 +44,7 @@ export default defineExecutorConfig({ dangerouslyAllowStdioMCP: process.env.EXECUTOR_ALLOW_STDIO_MCP === "true", }), graphqlHttpPlugin(), + wsdlHttpPlugin(), toolkitsPlugin({ activeToolkitSlug }), // First writable secret provider -> the default for `secrets.set`. encryptedSecretsPlugin({ key: resolveSecretKey() }), diff --git a/apps/host-selfhost/package.json b/apps/host-selfhost/package.json index 9d4da00172..56aee10f41 100644 --- a/apps/host-selfhost/package.json +++ b/apps/host-selfhost/package.json @@ -35,6 +35,7 @@ "@executor-js/plugin-openapi": "workspace:*", "@executor-js/plugin-provider-service-split": "workspace:*", "@executor-js/plugin-toolkits": "workspace:*", + "@executor-js/plugin-wsdl": "workspace:*", "@executor-js/react": "workspace:*", "@executor-js/runtime-quickjs": "workspace:*", "@executor-js/sdk": "workspace:*", diff --git a/apps/local/executor.config.ts b/apps/local/executor.config.ts index fa44b48c72..11779fc4f8 100644 --- a/apps/local/executor.config.ts +++ b/apps/local/executor.config.ts @@ -1,3 +1,4 @@ +import { wsdlHttpPlugin } from "@executor-js/plugin-wsdl/api"; import { defineExecutorConfig } from "@executor-js/sdk"; import { openApiHttpPlugin } from "@executor-js/plugin-openapi/api"; import { @@ -38,6 +39,7 @@ export default defineExecutorConfig({ }), mcpHttpPlugin({ dangerouslyAllowStdioMCP: true }), graphqlHttpPlugin(), + wsdlHttpPlugin(), toolkitsPlugin({ activeToolkitSlug }), // The durable file store must register before keychain: the first // writable provider becomes the default for minted OAuth tokens, and on diff --git a/apps/local/package.json b/apps/local/package.json index 79bebeef85..53f5bb6c61 100644 --- a/apps/local/package.json +++ b/apps/local/package.json @@ -41,6 +41,7 @@ "@executor-js/plugin-openapi": "workspace:*", "@executor-js/plugin-provider-service-split": "workspace:*", "@executor-js/plugin-toolkits": "workspace:*", + "@executor-js/plugin-wsdl": "workspace:*", "@executor-js/react": "workspace:*", "@executor-js/runtime-quickjs": "workspace:*", "@executor-js/sdk": "workspace:*", diff --git a/bun.lock b/bun.lock index 52aac3adf6..3480053896 100644 --- a/bun.lock +++ b/bun.lock @@ -76,6 +76,7 @@ "@executor-js/plugin-openapi": "workspace:*", "@executor-js/plugin-toolkits": "workspace:*", "@executor-js/plugin-workos-vault": "workspace:*", + "@executor-js/plugin-wsdl": "workspace:*", "@executor-js/react": "workspace:*", "@executor-js/runtime-dynamic-worker": "workspace:*", "@executor-js/runtime-quickjs": "workspace:*", @@ -135,6 +136,7 @@ "name": "@executor-js/desktop", "version": "1.6.8", "dependencies": { + "@executor-js/plugin-wsdl": "workspace:*", "@sentry/bun": "^10.57.0", "@sentry/electron": "7.19.0", "electron-log": "^5", @@ -186,6 +188,7 @@ "@executor-js/plugin-openapi": "workspace:*", "@executor-js/plugin-provider-service-split": "workspace:*", "@executor-js/plugin-toolkits": "workspace:*", + "@executor-js/plugin-wsdl": "workspace:*", "@executor-js/react": "workspace:*", "@executor-js/runtime-dynamic-worker": "workspace:*", "@executor-js/runtime-quickjs": "workspace:*", @@ -239,6 +242,7 @@ "@executor-js/plugin-openapi": "workspace:*", "@executor-js/plugin-provider-service-split": "workspace:*", "@executor-js/plugin-toolkits": "workspace:*", + "@executor-js/plugin-wsdl": "workspace:*", "@executor-js/react": "workspace:*", "@executor-js/runtime-quickjs": "workspace:*", "@executor-js/sdk": "workspace:*", @@ -296,6 +300,7 @@ "@executor-js/plugin-openapi": "workspace:*", "@executor-js/plugin-provider-service-split": "workspace:*", "@executor-js/plugin-toolkits": "workspace:*", + "@executor-js/plugin-wsdl": "workspace:*", "@executor-js/react": "workspace:*", "@executor-js/runtime-quickjs": "workspace:*", "@executor-js/sdk": "workspace:*", @@ -362,6 +367,7 @@ "@executor-js/plugin-mcp": "workspace:*", "@executor-js/plugin-openapi": "workspace:*", "@executor-js/plugin-toolkits": "workspace:*", + "@executor-js/plugin-wsdl": "workspace:*", "@executor-js/sdk": "workspace:*", "@kitlangton/terminal-control": "^0.3.0", "@modelcontextprotocol/sdk": "^1.29.0", @@ -1217,6 +1223,39 @@ "react", ], }, + "packages/plugins/wsdl": { + "name": "@executor-js/plugin-wsdl", + "version": "0.1.0", + "dependencies": { + "@executor-js/api": "workspace:*", + "@executor-js/sdk": "workspace:*", + "saxes": "^6.0.0", + }, + "devDependencies": { + "@effect/atom-react": "catalog:", + "@effect/vitest": "catalog:", + "@executor-js/react": "workspace:*", + "@types/node": "catalog:", + "@types/react": "catalog:", + "bun-types": "catalog:", + "effect": "catalog:", + "react": "catalog:", + "tsup": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:", + }, + "peerDependencies": { + "@effect/atom-react": "catalog:", + "@executor-js/react": "workspace:*", + "effect": "catalog:", + "react": "catalog:", + }, + "optionalPeers": [ + "@effect/atom-react", + "@executor-js/react", + "react", + ], + }, "packages/react": { "name": "@executor-js/react", "version": "1.4.71", @@ -1869,6 +1908,8 @@ "@executor-js/plugin-workos-vault": ["@executor-js/plugin-workos-vault@workspace:packages/plugins/workos-vault"], + "@executor-js/plugin-wsdl": ["@executor-js/plugin-wsdl@workspace:packages/plugins/wsdl"], + "@executor-js/react": ["@executor-js/react@workspace:packages/react"], "@executor-js/runtime-deno-subprocess": ["@executor-js/runtime-deno-subprocess@workspace:packages/kernel/runtime-deno-subprocess"], @@ -5345,6 +5386,8 @@ "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], + "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="], + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], "screenfull": ["screenfull@5.2.0", "", {}, "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA=="], @@ -5821,6 +5864,8 @@ "xmlbuilder2": ["xmlbuilder2@4.0.3", "", { "dependencies": { "@oozcitak/dom": "^2.0.2", "@oozcitak/infra": "^2.0.2", "@oozcitak/util": "^10.0.0", "js-yaml": "^4.1.1" } }, "sha512-bx8Q1STctnNaaDymWnkfQLKofs0mGNN7rLLapJlGuV3VlvegD7Ls4ggMjE3aUSWItCCzU0PEv45lI87iSigiCA=="], + "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="], + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], "xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="], diff --git a/e2e/package.json b/e2e/package.json index d605fedf77..9f08b0bf9d 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -29,6 +29,7 @@ "@executor-js/plugin-mcp": "workspace:*", "@executor-js/plugin-openapi": "workspace:*", "@executor-js/plugin-toolkits": "workspace:*", + "@executor-js/plugin-wsdl": "workspace:*", "@executor-js/sdk": "workspace:*", "@kitlangton/terminal-control": "^0.3.0", "@modelcontextprotocol/sdk": "^1.29.0", diff --git a/e2e/scenarios/wsdl-integration.test.ts b/e2e/scenarios/wsdl-integration.test.ts new file mode 100644 index 0000000000..ee2038196c --- /dev/null +++ b/e2e/scenarios/wsdl-integration.test.ts @@ -0,0 +1,74 @@ +import { randomBytes } from "node:crypto"; +import { expect } from "@effect/vitest"; +import { Effect, Exit } from "effect"; +import { composePluginApi } from "@executor-js/api/server"; +import { wsdlHttpPlugin } from "@executor-js/plugin-wsdl/api"; +import { ordersWsdl } from "@executor-js/plugin-wsdl/testing"; +import { AuthTemplateSlug, ConnectionName, IntegrationSlug } from "@executor-js/sdk/shared"; +import { scenario } from "../src/scenario"; +import { Api, Browser, Target } from "../src/services"; + +const api = composePluginApi([wsdlHttpPlugin()] as const); +scenario( + "WSDL · import a contract and discover SOAP operations", + {}, + Effect.gen(function* () { + const target = yield* Target; + const browser = yield* Browser; + const { client: makeClient } = yield* Api; + const identity = yield* target.newIdentity(); + const client = yield* makeClient(api, identity); + const slug = IntegrationSlug.make(`orders-${randomBytes(4).toString("hex")}`); + yield* Effect.ensuring( + Effect.gen(function* () { + const rejected = yield* Effect.exit( + client.wsdl.addIntegration({ + payload: { + slug, + name: "Orders", + wsdl: ordersWsdl.replace('style="document"', 'style="rpc"'), + }, + }), + ); + expect(Exit.isFailure(rejected), "RPC contracts must fail import").toBe(true); + yield* browser.session(identity, async ({ page, step }) => { + await step("Open the WSDL import form", async () => { + await page.goto("/integrations/add/wsdl", { waitUntil: "networkidle" }); + await page.getByLabel("WSDL contract").waitFor(); + }); + await step("Import the Orders WSDL contract", async () => { + await page.getByLabel("Integration name").fill("Orders SOAP"); + await page.getByLabel("Namespace", { exact: true }).fill(slug); + await page.getByLabel("WSDL contract").fill(ordersWsdl); + await page.getByRole("button", { name: "Add WSDL integration" }).click(); + await page.waitForURL(`**/integrations/${slug}`, { timeout: 30_000 }); + await page.getByText("Orders SOAP", { exact: true }).first().waitFor(); + await page.getByText("No connections yet", { exact: true }).waitFor(); + expect(await page.getByText("Orders SOAP", { exact: true }).count()).toBeGreaterThan(0); + }); + }); + yield* client.connections.create({ + payload: { + owner: "org", + name: ConnectionName.make("main"), + integration: slug, + template: AuthTemplateSlug.make("none"), + values: {}, + }, + }); + const tools = yield* client.tools.list({ query: {} }); + expect(tools.filter((tool) => tool.integration === slug).map((tool) => tool.name)).toEqual([ + "GetOrder", + ]); + }), + Effect.gen(function* () { + yield* client.connections + .remove({ + params: { owner: "org", integration: slug, name: ConnectionName.make("main") }, + }) + .pipe(Effect.ignore); + yield* client.integrations.remove({ params: { slug } }).pipe(Effect.ignore); + }), + ); + }), +); diff --git a/packages/plugins/wsdl/CHANGELOG.md b/packages/plugins/wsdl/CHANGELOG.md new file mode 100644 index 0000000000..14e7fa4c4b --- /dev/null +++ b/packages/plugins/wsdl/CHANGELOG.md @@ -0,0 +1 @@ +# @executor-js/plugin-wsdl diff --git a/packages/plugins/wsdl/README.md b/packages/plugins/wsdl/README.md new file mode 100644 index 0000000000..ea9d0b0750 --- /dev/null +++ b/packages/plugins/wsdl/README.md @@ -0,0 +1,58 @@ +# WSDL / SOAP plugin + +Import a self-contained WSDL contract in **Add integration → WSDL / SOAP**, or +call the `wsdl.addIntegration` SDK extension / static tool. Create a connection +against the integration to expose one tool per operation. Tool calls take +`{ body: ... }`, where `body` is the selected input element's value. + +```ts +import { IntegrationSlug } from "@executor-js/sdk"; +import { wsdlPlugin } from "@executor-js/plugin-wsdl"; + +// Include wsdlPlugin() in your executor's plugins. +const addOrders = executor.wsdl.addIntegration({ + slug: IntegrationSlug.make("orders"), + name: "Orders", + wsdl: contractXml, + service: "Orders", // required when service/port selection is ambiguous + port: "OrdersPort", + // endpoint: "https://example.com/soap", // optional override +}); +``` + +The SDK extension returns an Effect; compose it with your application's Effect +runtime to execute it. The HTTP variant is `wsdlHttpPlugin` +from `@executor-js/plugin-wsdl/api`, with `POST /wsdl/integrations`. + +## Supported profile + +- WSDL 1.1, SOAP 1.1 with UTF-8 XML over HTTP(S), document/literal request-response operations. +- One element body part per input/output; wrapped and bare bodies retain their + declared XML shape. Service and port selection use their local names. +- Inline XSD schemas, global element references, named or inline complex types + with sequences, optional/repeated elements, and nillable values. +- XSD `string`, `boolean`, `int`, `integer`, and `decimal`. Unbounded integers + and decimals use validated JSON strings, preserving precision. +- SOAP faults become tool failures, including on HTTP error statuses. +- SDK/API registration accepts shared `authenticationTemplate` entries of kind + `apikey` (header/query placements) or `none`. Credentials come from Executor + connections. The initial import form creates an unauthenticated integration. + +Unsupported contracts fail import: external WSDL/XSD imports/includes, WSDL 2, +SOAP 1.2, RPC/encoded bindings, attachments, SOAP header bindings, WS-Security, +recursive types, and XSD constructs outside the profile above. Fault details +are not decoded into typed values. No automatic SOAP calls are used as health +checks, and all operations require approval by default. + +XML rejects DTD/entity declarations, limits input/response size to 2 MB, depth +to 64, and elements to 50,000. Expanded schemas are limited to 10,000 elements +and 32 levels; repeated fields permit at most 10,000 values. HTTP calls use the +host's injected Effect HttpClient, a 110-second deadline, and no automatic retry. + +## Development + +Run `bun run typecheck` and `bun run test` from this package. The cross-target +browser scenario is `e2e/scenarios/wsdl-integration.test.ts`. + +Protocol references: [WSDL 1.1](https://www.w3.org/TR/wsdl.html) and +[SOAP 1.1](https://www.w3.org/TR/2000/NOTE-SOAP-20000508/). diff --git a/packages/plugins/wsdl/package.json b/packages/plugins/wsdl/package.json new file mode 100644 index 0000000000..5e19ff2754 --- /dev/null +++ b/packages/plugins/wsdl/package.json @@ -0,0 +1,84 @@ +{ + "name": "@executor-js/plugin-wsdl", + "version": "0.1.0", + "license": "MIT", + "files": [ + "dist" + ], + "type": "module", + "exports": { + ".": "./src/index.ts", + "./api": "./src/api.ts", + "./client": "./src/client.tsx", + "./testing": "./src/fixture.ts" + }, + "publishConfig": { + "access": "public", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "./api": { + "import": { + "types": "./dist/api.d.ts", + "default": "./dist/api.js" + } + }, + "./client": { + "import": { + "types": "./dist/client.d.ts", + "default": "./dist/client.js" + } + }, + "./testing": { + "import": { + "types": "./dist/fixture.d.ts", + "default": "./dist/testing.js" + } + } + } + }, + "scripts": { + "typecheck": "tsgo --noEmit", + "test": "vitest run", + "build": "tsup && tsc --declaration --emitDeclarationOnly --outDir dist --rootDir src" + }, + "dependencies": { + "@executor-js/api": "workspace:*", + "@executor-js/sdk": "workspace:*", + "saxes": "^6.0.0" + }, + "devDependencies": { + "@effect/atom-react": "catalog:", + "@effect/vitest": "catalog:", + "@executor-js/react": "workspace:*", + "@types/node": "catalog:", + "@types/react": "catalog:", + "bun-types": "catalog:", + "effect": "catalog:", + "react": "catalog:", + "tsup": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" + }, + "peerDependencies": { + "@effect/atom-react": "catalog:", + "@executor-js/react": "workspace:*", + "effect": "catalog:", + "react": "catalog:" + }, + "peerDependenciesMeta": { + "@executor-js/react": { + "optional": true + }, + "@effect/atom-react": { + "optional": true + }, + "react": { + "optional": true + } + } +} diff --git a/packages/plugins/wsdl/src/api.ts b/packages/plugins/wsdl/src/api.ts new file mode 100644 index 0000000000..e8fd71ec80 --- /dev/null +++ b/packages/plugins/wsdl/src/api.ts @@ -0,0 +1,28 @@ +import { Context, Effect } from "effect"; +import { HttpApiBuilder } from "effect/unstable/httpapi"; +import { addGroup, capture } from "@executor-js/api"; +import { definePlugin } from "@executor-js/sdk/core"; +import { wsdlPlugin } from "./index"; + +import { WsdlGroup } from "./group"; +export { WsdlGroup } from "./group"; +export class WsdlExtension extends Context.Service< + WsdlExtension, + ReturnType["extension"]>> +>()("WsdlExtension") {} +const Handlers = HttpApiBuilder.group(addGroup(WsdlGroup), "wsdl", (handlers) => + handlers.handle("addIntegration", ({ payload }) => + capture( + Effect.gen(function* () { + const extension = yield* WsdlExtension; + return yield* extension.addIntegration(payload); + }), + ), + ), +); +export const wsdlHttpPlugin = definePlugin(() => ({ + ...wsdlPlugin(), + routes: () => WsdlGroup, + handlers: () => Handlers, + extensionService: WsdlExtension, +})); diff --git a/packages/plugins/wsdl/src/client.tsx b/packages/plugins/wsdl/src/client.tsx new file mode 100644 index 0000000000..665fdfb561 --- /dev/null +++ b/packages/plugins/wsdl/src/client.tsx @@ -0,0 +1,121 @@ +import { useState } from "react"; +import { Exit, Option, Predicate } from "effect"; +import { defineClientPlugin, createPluginAtomClient, useAtomSet } from "@executor-js/sdk/client"; +import { IntegrationSlug } from "@executor-js/sdk/shared"; +import { Button } from "@executor-js/react/components/button"; +import { Input } from "@executor-js/react/components/input"; +import { Textarea } from "@executor-js/react/components/textarea"; +import { integrationWriteKeys } from "@executor-js/react/api/reactivity-keys"; +import { WsdlGroup } from "./group"; + +const Client = createPluginAtomClient(WsdlGroup); +const addAtom = Client.mutation("wsdl", "addIntegration"); +function AddWsdl(props: { onComplete: (slug?: string) => void; onCancel: () => void }) { + const [name, setName] = useState(""); + const [slug, setSlug] = useState(""); + const [wsdl, setWsdl] = useState(""); + const [service, setService] = useState(""); + const [port, setPort] = useState(""); + const [endpoint, setEndpoint] = useState(""); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(); + const add = useAtomSet(addAtom, { mode: "promiseExit" }); + const submit = async () => { + if (busy) return; + setBusy(true); + setError(undefined); + const result = await add({ + payload: { + name, + slug: IntegrationSlug.make(slug), + wsdl, + service: service || undefined, + port: port || undefined, + endpoint: endpoint || undefined, + }, + reactivityKeys: integrationWriteKeys, + }); + setBusy(false); + if (Exit.isFailure(result)) { + const failure = Exit.findErrorOption(result); + setError( + Option.isSome(failure) && Predicate.isTagged(failure.value, "WsdlError") + ? failure.value.message + : "Could not add WSDL integration", + ); + return; + } + props.onComplete(result.value.slug); + }; + return ( +
+

+ Import a self-contained WSDL 1.1 contract using SOAP 1.1 document/literal. External imports, + SOAP headers, and RPC bindings are not supported. +

+ setName(e.target.value)} + disabled={busy} + /> + setSlug(e.target.value)} + disabled={busy} + /> +