Skip to content

Commit 71d8ae1

Browse files
os-zhuangclaude
andauthored
feat(connector-openapi): OpenAPI 3.x → Connector generator (ADR-0023) (#1425)
* feat(connector-openapi): OpenAPI 3.x → Connector generator (ADR-0023) Add @objectstack/connector-openapi, mirroring connector-rest / connector-mcp: - createOpenApiConnector turns an OpenAPI 3.x document into a Connector definition + handler map (one operation -> one action). It reuses @objectstack/connector-rest's request handler for the HTTP/auth transport (one shared implementation, per ADR-0022) and returns the same { definition, handlers } shape consumed by engine.registerConnector — no new engine surface. - Input schemas assembled as { path, query, header, body } from parameters + requestBody; output schema from the 200/2xx/default JSON response; action names from operationId with a deterministic slug fallback and de-duping. - Auth inferred from components.securitySchemes when no credentials are supplied; supplied RestAuth is reflected in the definition metadata. - include allowlist for trimming large specs. - registerOpenApiConnector plugin mirroring registerRestConnector / registerMcpConnector. - 12 unit tests (metadata, name/slug fallback, schema assembly, output schema, auth inference, allowlist, handler path/query/body dispatch, missing-base-URL error, engine registration). Updates ADR-0023 status to mark the generator package implemented; the CLI and worked example remain noted follow-ups. * fix(connector-openapi): self-contained transport; merge main; changeset Make @objectstack/connector-openapi self-contained and correct against the real connector/spec APIs (fixes the initial scaffold): - Drop the @objectstack/connector-rest dependency; inline the static-auth HTTP transport (build URL, apply auth, JSON-encode body, normalise to { status, ok, body }) mirroring connector-rest, so the package depends only on @objectstack/core + @objectstack/spec like every sibling connector. - Use the real ConnectorAction `key` field, the `{ type }` auth discriminator, import Connector from @objectstack/spec/integration, and return { def, handlers } — matching the registry contract used by connector-rest/mcp. - Fix tsconfig (rootDir/outDir/types) so dts builds. Resolves the "Validate Package Dependencies" failure: add @objectstack/connector-openapi to the changesets `fixed` group, add a changeset entry, and refresh pnpm-lock.yaml. Update ADR-0023 to describe the self-contained transport. Merge latest main. Verified locally: turbo build (esm+cjs+dts), 10/10 vitest tests, eslint, check-changeset-fixed.mjs, and pnpm install --frozen-lockfile all pass. --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 03fd7f0 commit 71d8ae1

12 files changed

Lines changed: 829 additions & 5 deletions

.changeset/config.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
{
22
"$schema": "https://unpkg.com/@changesets/config@3.1.2/schema.json",
3-
"changelog": ["@changesets/cli/changelog", { "repo": "objectstack-ai/spec" }],
3+
"changelog": [
4+
"@changesets/cli/changelog",
5+
{
6+
"repo": "objectstack-ai/spec"
7+
}
8+
],
49
"commit": false,
510
"fixed": [
611
[
@@ -71,7 +76,8 @@
7176
"@objectstack/embedder-openai",
7277
"@objectstack/account",
7378
"create-objectstack",
74-
"objectstack-vscode"
79+
"objectstack-vscode",
80+
"@objectstack/connector-openapi"
7581
]
7682
],
7783
"linked": [],

.changeset/openapi-to-connector.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@objectstack/connector-openapi": minor
3+
---
4+
5+
Add `@objectstack/connector-openapi` — generate an ObjectStack connector from a declarative OpenAPI 3.x document (ADR-0023). One operation becomes one connector action; a single generic handler drives a self-contained static-auth HTTP transport (mirroring `@objectstack/connector-rest`). The generated `type: 'api'` connector registers via `engine.registerConnector(def, handlers)` with no new engine surface, and supports an `include` allowlist for trimming large specs.

docs/adr/0023-openapi-to-connector-generator.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,8 @@ A spec that declares OAuth2 is still importable open-source — we generate the
141141

142142
## Status & follow-ups
143143

144-
- **This ADR changes no shipped code.** It records the decision to add a generator package on top of the existing connector baseline.
145-
- Follow-up: scaffold `packages/connectors/connector-openapi` mirroring `connector-rest`, with `createOpenApiConnector` + `ConnectorOpenApiPlugin`.
146-
- Follow-up: a small `openapi-to-connector` CLI that emits a reviewable `*.connector.json`.
144+
-**Implemented:** `packages/connectors/connector-openapi` mirrors `connector-rest` / `connector-mcp`, exporting `createOpenApiConnector` (one operation → one action) + `registerOpenApiConnector`. Each generated action drives one shared static-auth HTTP transport that mirrors `connector-rest` (build URL from base+path+query, apply static auth, JSON-encode the body, normalise the response to `{ status, ok, body }`) — kept inline so the package stays self-contained (depends only on `@objectstack/core` + `@objectstack/spec`, like its sibling connectors), and returns the `{ def, handlers }` shape consumed by `engine.registerConnector`. Input schemas are assembled as `{ path, query, header, body }` from `parameters` + `requestBody`; output schemas from the `200`/`2xx`/`default` JSON response; static auth (`none`/`api-key`/`basic`/`bearer`) is supplied by the caller. Includes an `include` allowlist for trimming large specs.
145+
- Follow-up: a small `openapi-to-connector` CLI that emits a reviewable `*.connector.json` (deferred — needs a multi-entry build, whereas every connector package currently uses the shared single-entry `tsup` config).
147146
- Follow-up: a worked example (e.g. GitHub or Stripe public OpenAPI → a handful of allowlisted actions) under `examples/`, paralleling the worked `connector_action` example from ADR-0022.
147+
- Follow-up: YAML spec input and a `$ref` deref pass for specs the caller has not pre-dereferenced.
148148
- Cross-reference: see [ADR-0024](./0024-mcp-connectors.md) for the complementary path — wrapping live **MCP servers** as connectors when no OpenAPI spec exists but an MCP server does.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved.
2+
"Business Source License" is a trademark of MariaDB Corporation Ab.
3+
4+
Parameters
5+
6+
Licensor: ObjectStack AI LLC
7+
Licensed Work: ObjectStack Runtime: the BSL-licensed packages
8+
of the ObjectStack monorepo as listed in LICENSING.md.
9+
Copyright (c) 2026 ObjectStack AI LLC.
10+
Additional Use Grant: You may make production use of the Licensed Work, provided
11+
Your use does not include offering the Licensed Work to third
12+
parties on a hosted or embedded basis in order to compete with
13+
ObjectStack AI LLC's paid version(s) of the Licensed Work. For purposes
14+
of this license:
15+
16+
A "competitive offering" is a Product that is offered to third
17+
parties on a paid basis, including through paid support
18+
arrangements, that significantly overlaps with the capabilities
19+
of ObjectStack AI LLC's paid version(s) of the Licensed Work. If Your
20+
Product is not a competitive offering when You first make it
21+
generally available, it will not become a competitive offering
22+
later due to ObjectStack AI LLC releasing a new version of the Licensed
23+
Work with additional capabilities. In addition, Products that
24+
are not provided on a paid basis are not competitive.
25+
26+
"Product" means software that is offered to end users to manage
27+
in their own environments or offered as a service on a hosted
28+
basis.
29+
30+
"Embedded" means including the source code or executable code
31+
from the Licensed Work in a competitive offering. "Embedded"
32+
also means packaging the competitive offering in such a way
33+
that the Licensed Work must be accessed or downloaded for the
34+
competitive offering to operate.
35+
36+
Hosting or using the Licensed Work(s) for internal purposes
37+
within an organization is not considered a competitive
38+
offering. ObjectStack AI LLC considers your organization to include all
39+
of your affiliates under common control.
40+
41+
For binding interpretive guidance on using ObjectStack AI LLC products
42+
under the Business Source License, please visit our FAQ.
43+
(see LICENSING.md in this repository)
44+
Change Date: Four years from the date the Licensed Work is published.
45+
Change License: Apache License, Version 2.0
46+
47+
For information about alternative licensing arrangements for the Licensed Work,
48+
please contact licensing@objectstack.dev.
49+
50+
Notice
51+
52+
Business Source License 1.1
53+
54+
Terms
55+
56+
The Licensor hereby grants you the right to copy, modify, create derivative
57+
works, redistribute, and make non-production use of the Licensed Work. The
58+
Licensor may make an Additional Use Grant, above, permitting limited production use.
59+
60+
Effective on the Change Date, or the fourth anniversary of the first publicly
61+
available distribution of a specific version of the Licensed Work under this
62+
License, whichever comes first, the Licensor hereby grants you rights under
63+
the terms of the Change License, and the rights granted in the paragraph
64+
above terminate.
65+
66+
If your use of the Licensed Work does not comply with the requirements
67+
currently in effect as described in this License, you must purchase a
68+
commercial license from the Licensor, its affiliated entities, or authorized
69+
resellers, or you must refrain from using the Licensed Work.
70+
71+
All copies of the original and modified Licensed Work, and derivative works
72+
of the Licensed Work, are subject to this License. This License applies
73+
separately for each version of the Licensed Work and the Change Date may vary
74+
for each version of the Licensed Work released by Licensor.
75+
76+
You must conspicuously display this License on each original or modified copy
77+
of the Licensed Work. If you receive the Licensed Work in original or
78+
modified form from a third party, the terms and conditions set forth in this
79+
License apply to your use of that work.
80+
81+
Any use of the Licensed Work in violation of this License will automatically
82+
terminate your rights under this License for the current and all other
83+
versions of the Licensed Work.
84+
85+
This License does not grant you any right in any trademark or logo of
86+
Licensor or its affiliates (provided that you may use a trademark or logo of
87+
Licensor as expressly required by this License).
88+
89+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
90+
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
91+
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
92+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
93+
TITLE.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "@objectstack/connector-openapi",
3+
"version": "7.3.0",
4+
"license": "Apache-2.0",
5+
"description": "OpenAPI 3.x connector generator for ObjectStack — turns a declarative OpenAPI document into connector actions on the automation engine's registry, with a self-contained static-auth HTTP transport (ADR-0023).",
6+
"main": "dist/index.js",
7+
"types": "dist/index.d.ts",
8+
"exports": {
9+
".": {
10+
"types": "./dist/index.d.ts",
11+
"import": "./dist/index.mjs",
12+
"require": "./dist/index.js"
13+
}
14+
},
15+
"scripts": {
16+
"build": "tsup --config ../../../tsup.config.ts",
17+
"test": "vitest run --passWithNoTests"
18+
},
19+
"dependencies": {
20+
"@objectstack/core": "workspace:*",
21+
"@objectstack/spec": "workspace:*"
22+
},
23+
"devDependencies": {
24+
"@objectstack/service-automation": "workspace:*",
25+
"@types/node": "^25.9.1",
26+
"typescript": "^6.0.3",
27+
"vitest": "^4.1.7"
28+
},
29+
"keywords": [
30+
"objectstack",
31+
"connector",
32+
"openapi",
33+
"swagger",
34+
"integration",
35+
"api"
36+
]
37+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import { describe, it, expect, vi } from 'vitest';
4+
import { registerOpenApiConnector, type ConnectorRegistrySurface } from './connector-openapi-plugin.js';
5+
import type { OpenApiDocument } from './openapi-connector.js';
6+
7+
const doc: OpenApiDocument = {
8+
info: { title: 'Mini' },
9+
servers: [{ url: 'https://api.mini.example.com' }],
10+
paths: {
11+
'/ping': { get: { operationId: 'ping', responses: { '200': { description: 'ok' } } } },
12+
},
13+
};
14+
15+
describe('registerOpenApiConnector', () => {
16+
it('registers the generated definition + handlers on the registry', () => {
17+
const registerConnector = vi.fn();
18+
const registry = { registerConnector, unregisterConnector: vi.fn() } as unknown as ConnectorRegistrySurface;
19+
20+
const name = registerOpenApiConnector(registry, { document: doc });
21+
22+
expect(name).toBe('mini');
23+
expect(registerConnector).toHaveBeenCalledTimes(1);
24+
const [def, handlers] = registerConnector.mock.calls[0];
25+
expect(def.name).toBe('mini');
26+
expect(typeof handlers.ping).toBe('function');
27+
});
28+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import type { Connector } from '@objectstack/spec/integration';
4+
import { createOpenApiConnector, type OpenApiConnectorConfig } from './openapi-connector.js';
5+
6+
/**
7+
* Minimal surface of the automation engine this helper depends on — the
8+
* connector registry from ADR-0018 §Addendum. Kept structural so callers need
9+
* no runtime dependency on `@objectstack/service-automation` (mirrors
10+
* connector-rest / connector-mcp).
11+
*/
12+
export interface ConnectorRegistrySurface {
13+
registerConnector(
14+
def: Connector,
15+
handlers: Record<
16+
string,
17+
(input: Record<string, unknown>, ctx: unknown) => Promise<Record<string, unknown>>
18+
>,
19+
): void;
20+
unregisterConnector(name: string): void;
21+
}
22+
23+
/**
24+
* Generate an OpenAPI-backed connector and register it on the engine's connector
25+
* registry so the baseline `connector_action` node can dispatch to the generated
26+
* actions (ADR-0023). Returns the registered connector name.
27+
*/
28+
export function registerOpenApiConnector(registry: ConnectorRegistrySurface, config: OpenApiConnectorConfig): string {
29+
const { def, handlers } = createOpenApiConnector(config);
30+
registry.registerConnector(def, handlers);
31+
return def.name;
32+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* @objectstack/connector-openapi
5+
*
6+
* Generate an ObjectStack {@link Connector} from a declarative OpenAPI 3.x
7+
* document (ADR-0023). One operation becomes one connector action; a single
8+
* generic handler drives a self-contained static-auth HTTP transport (mirroring
9+
* `@objectstack/connector-rest`). The generated connector is an ordinary
10+
* `type: 'api'` connector — registered via `engine.registerConnector` with no
11+
* new engine surface.
12+
*
13+
* Open-source scope: static auth only (`none` / `api-key` / `basic` / `bearer`),
14+
* credentials supplied by the caller. Managed OAuth2, credential vaulting, and
15+
* per-tenant lifecycle are the enterprise tier (ADR-0015 / 0022).
16+
*/
17+
18+
export {
19+
createOpenApiConnector,
20+
type OpenApiConnectorBundle,
21+
type OpenApiConnectorConfig,
22+
type OpenApiDocument,
23+
type OpenApiPathItem,
24+
type OpenApiOperation,
25+
type OpenApiParameter,
26+
type OpenApiRequestBody,
27+
type OpenApiResponse,
28+
type OpenApiSecurityScheme,
29+
type OperationInfo,
30+
type RestAuth,
31+
type JsonSchema,
32+
} from './openapi-connector.js';
33+
export {
34+
registerOpenApiConnector,
35+
type ConnectorRegistrySurface,
36+
} from './connector-openapi-plugin.js';

0 commit comments

Comments
 (0)