@objectstack/connector-openapi 7.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +22 -0
- package/CHANGELOG.md +23 -0
- package/LICENSE +93 -0
- package/dist/index.d.mts +147 -0
- package/dist/index.d.ts +147 -0
- package/dist/index.js +252 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +224 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +37 -0
- package/src/connector-openapi-plugin.test.ts +28 -0
- package/src/connector-openapi-plugin.ts +32 -0
- package/src/index.ts +36 -0
- package/src/openapi-connector.test.ts +141 -0
- package/src/openapi-connector.ts +414 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
> @objectstack/connector-openapi@7.4.0 build /home/runner/work/framework/framework/packages/connectors/connector-openapi
|
|
3
|
+
> tsup --config ../../../tsup.config.ts
|
|
4
|
+
|
|
5
|
+
[34mCLI[39m Building entry: src/index.ts
|
|
6
|
+
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
|
+
[34mCLI[39m tsup v8.5.1
|
|
8
|
+
[34mCLI[39m Using tsup config: /home/runner/work/framework/framework/tsup.config.ts
|
|
9
|
+
[34mCLI[39m Target: es2020
|
|
10
|
+
[34mCLI[39m Cleaning output folder
|
|
11
|
+
[34mESM[39m Build start
|
|
12
|
+
[34mCJS[39m Build start
|
|
13
|
+
[32mCJS[39m [1mdist/index.js [22m[32m8.82 KB[39m
|
|
14
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m25.11 KB[39m
|
|
15
|
+
[32mCJS[39m ⚡️ Build success in 94ms
|
|
16
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m7.73 KB[39m
|
|
17
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m23.76 KB[39m
|
|
18
|
+
[32mESM[39m ⚡️ Build success in 108ms
|
|
19
|
+
[34mDTS[39m Build start
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 8511ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m6.11 KB[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m6.11 KB[39m
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# @objectstack/connector-openapi
|
|
2
|
+
|
|
3
|
+
## 7.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 71d8ae1: 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.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [23c7107]
|
|
12
|
+
- Updated dependencies [c72daad]
|
|
13
|
+
- Updated dependencies [f115182]
|
|
14
|
+
- Updated dependencies [2faf9f2]
|
|
15
|
+
- Updated dependencies [2faf9f2]
|
|
16
|
+
- Updated dependencies [2faf9f2]
|
|
17
|
+
- Updated dependencies [58b450b]
|
|
18
|
+
- Updated dependencies [82eb6cf]
|
|
19
|
+
- Updated dependencies [13d8653]
|
|
20
|
+
- Updated dependencies [ff3d006]
|
|
21
|
+
- Updated dependencies [5e831de]
|
|
22
|
+
- @objectstack/spec@7.4.0
|
|
23
|
+
- @objectstack/core@7.4.0
|
package/LICENSE
ADDED
|
@@ -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.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Connector } from '@objectstack/spec/integration';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OpenAPI connector generator — turns a declarative OpenAPI 3.x document into a
|
|
5
|
+
* {@link Connector} definition + handler map (ADR-0023).
|
|
6
|
+
*
|
|
7
|
+
* Each OpenAPI operation maps to one connector action; a single generic handler
|
|
8
|
+
* (closing over the operation's method + path template) drives one shared HTTP
|
|
9
|
+
* request implementation. That transport mirrors `@objectstack/connector-rest`
|
|
10
|
+
* (build URL from base+path+query, apply static auth, JSON-encode the body,
|
|
11
|
+
* normalise the response to `{ status, ok, body }`) — kept inline so this package
|
|
12
|
+
* stays self-contained, depending only on `@objectstack/core` + `@objectstack/spec`
|
|
13
|
+
* like its sibling connectors. The output is an ordinary `type: 'api'` connector,
|
|
14
|
+
* registered via `engine.registerConnector(def, handlers)` exactly like a
|
|
15
|
+
* hand-written one — the registry, the `connector_action` node, the discovery
|
|
16
|
+
* route, and the Studio palette never know it came from OpenAPI.
|
|
17
|
+
*
|
|
18
|
+
* Open-source scope: **static** auth only (`none` / `api-key` / `basic` /
|
|
19
|
+
* `bearer`), with credentials supplied by the caller. Managed OAuth2, credential
|
|
20
|
+
* vaulting, and per-tenant lifecycle are the enterprise tier (ADR-0015 / 0022).
|
|
21
|
+
*/
|
|
22
|
+
/** Static auth understood by the generated connector (the open-source subset). */
|
|
23
|
+
type RestAuth = Extract<Connector['authentication'], {
|
|
24
|
+
type: 'none' | 'api-key' | 'basic' | 'bearer';
|
|
25
|
+
}>;
|
|
26
|
+
/** Handler signature accepted by the connector registry (ADR-0018 §Addendum). */
|
|
27
|
+
type ConnectorHandler = (input: Record<string, unknown>, ctx: unknown) => Promise<Record<string, unknown>>;
|
|
28
|
+
/** A connector definition paired with its action handlers, ready for registerConnector(). */
|
|
29
|
+
interface OpenApiConnectorBundle {
|
|
30
|
+
def: Connector;
|
|
31
|
+
handlers: Record<string, ConnectorHandler>;
|
|
32
|
+
}
|
|
33
|
+
/** A free-form JSON Schema fragment (matches ConnectorAction input/outputSchema). */
|
|
34
|
+
type JsonSchema = Record<string, unknown>;
|
|
35
|
+
/** Minimal subset of an OpenAPI 3.x document consumed by the generator.
|
|
36
|
+
* The caller is responsible for loading and de-referencing ($ref) the doc. */
|
|
37
|
+
interface OpenApiDocument {
|
|
38
|
+
openapi?: string;
|
|
39
|
+
info?: {
|
|
40
|
+
title?: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
version?: string;
|
|
43
|
+
};
|
|
44
|
+
servers?: {
|
|
45
|
+
url: string;
|
|
46
|
+
}[];
|
|
47
|
+
paths?: Record<string, OpenApiPathItem>;
|
|
48
|
+
components?: {
|
|
49
|
+
securitySchemes?: Record<string, OpenApiSecurityScheme>;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
interface OpenApiPathItem {
|
|
53
|
+
[method: string]: OpenApiOperation | unknown;
|
|
54
|
+
}
|
|
55
|
+
interface OpenApiOperation {
|
|
56
|
+
operationId?: string;
|
|
57
|
+
summary?: string;
|
|
58
|
+
description?: string;
|
|
59
|
+
tags?: string[];
|
|
60
|
+
parameters?: OpenApiParameter[];
|
|
61
|
+
requestBody?: OpenApiRequestBody;
|
|
62
|
+
responses?: Record<string, OpenApiResponse>;
|
|
63
|
+
}
|
|
64
|
+
interface OpenApiParameter {
|
|
65
|
+
name: string;
|
|
66
|
+
in: 'path' | 'query' | 'header' | 'cookie';
|
|
67
|
+
required?: boolean;
|
|
68
|
+
description?: string;
|
|
69
|
+
schema?: JsonSchema;
|
|
70
|
+
}
|
|
71
|
+
interface OpenApiRequestBody {
|
|
72
|
+
required?: boolean;
|
|
73
|
+
description?: string;
|
|
74
|
+
content?: Record<string, {
|
|
75
|
+
schema?: JsonSchema;
|
|
76
|
+
}>;
|
|
77
|
+
}
|
|
78
|
+
interface OpenApiResponse {
|
|
79
|
+
description?: string;
|
|
80
|
+
content?: Record<string, {
|
|
81
|
+
schema?: JsonSchema;
|
|
82
|
+
}>;
|
|
83
|
+
}
|
|
84
|
+
interface OpenApiSecurityScheme {
|
|
85
|
+
type: 'apiKey' | 'http' | 'oauth2' | 'openIdConnect';
|
|
86
|
+
name?: string;
|
|
87
|
+
in?: 'header' | 'query' | 'cookie';
|
|
88
|
+
scheme?: string;
|
|
89
|
+
}
|
|
90
|
+
/** Flattened view of a single operation, passed to the `include` predicate. */
|
|
91
|
+
interface OperationInfo {
|
|
92
|
+
operationId?: string;
|
|
93
|
+
method: string;
|
|
94
|
+
path: string;
|
|
95
|
+
tags?: string[];
|
|
96
|
+
summary?: string;
|
|
97
|
+
description?: string;
|
|
98
|
+
}
|
|
99
|
+
/** Configuration for {@link createOpenApiConnector}. */
|
|
100
|
+
interface OpenApiConnectorConfig {
|
|
101
|
+
/** Connector machine name (snake_case). Defaults to a slug of info.title. */
|
|
102
|
+
name?: string;
|
|
103
|
+
/** Human-friendly label. Defaults to info.title (then name). */
|
|
104
|
+
label?: string;
|
|
105
|
+
/** Description. Defaults to info.description. */
|
|
106
|
+
description?: string;
|
|
107
|
+
/** Icon identifier for the Studio palette. Defaults to `globe`. */
|
|
108
|
+
icon?: string;
|
|
109
|
+
/** The parsed OpenAPI 3.x document (caller loads/derefs it). */
|
|
110
|
+
document: OpenApiDocument;
|
|
111
|
+
/** Override the base URL (else servers[0].url). */
|
|
112
|
+
baseUrl?: string;
|
|
113
|
+
/** Static auth with credentials. Defaults to `{ type: 'none' }`. */
|
|
114
|
+
auth?: RestAuth;
|
|
115
|
+
/** Headers merged into every request (request-level headers win). */
|
|
116
|
+
defaultHeaders?: Record<string, string>;
|
|
117
|
+
/** Only include operations for which this predicate returns true (allowlist). */
|
|
118
|
+
include?: (op: OperationInfo) => boolean;
|
|
119
|
+
/** Injected fetch implementation (defaults to global `fetch`). */
|
|
120
|
+
fetchImpl?: typeof fetch;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Build an OpenAPI connector definition and its handler map.
|
|
124
|
+
*
|
|
125
|
+
* @returns the `Connector` definition (`def`) and a `handlers` record keyed by
|
|
126
|
+
* action key, suitable for `engine.registerConnector(def, handlers)`.
|
|
127
|
+
*/
|
|
128
|
+
declare function createOpenApiConnector(config: OpenApiConnectorConfig): OpenApiConnectorBundle;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Minimal surface of the automation engine this helper depends on — the
|
|
132
|
+
* connector registry from ADR-0018 §Addendum. Kept structural so callers need
|
|
133
|
+
* no runtime dependency on `@objectstack/service-automation` (mirrors
|
|
134
|
+
* connector-rest / connector-mcp).
|
|
135
|
+
*/
|
|
136
|
+
interface ConnectorRegistrySurface {
|
|
137
|
+
registerConnector(def: Connector, handlers: Record<string, (input: Record<string, unknown>, ctx: unknown) => Promise<Record<string, unknown>>>): void;
|
|
138
|
+
unregisterConnector(name: string): void;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Generate an OpenAPI-backed connector and register it on the engine's connector
|
|
142
|
+
* registry so the baseline `connector_action` node can dispatch to the generated
|
|
143
|
+
* actions (ADR-0023). Returns the registered connector name.
|
|
144
|
+
*/
|
|
145
|
+
declare function registerOpenApiConnector(registry: ConnectorRegistrySurface, config: OpenApiConnectorConfig): string;
|
|
146
|
+
|
|
147
|
+
export { type ConnectorRegistrySurface, type JsonSchema, type OpenApiConnectorBundle, type OpenApiConnectorConfig, type OpenApiDocument, type OpenApiOperation, type OpenApiParameter, type OpenApiPathItem, type OpenApiRequestBody, type OpenApiResponse, type OpenApiSecurityScheme, type OperationInfo, type RestAuth, createOpenApiConnector, registerOpenApiConnector };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Connector } from '@objectstack/spec/integration';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OpenAPI connector generator — turns a declarative OpenAPI 3.x document into a
|
|
5
|
+
* {@link Connector} definition + handler map (ADR-0023).
|
|
6
|
+
*
|
|
7
|
+
* Each OpenAPI operation maps to one connector action; a single generic handler
|
|
8
|
+
* (closing over the operation's method + path template) drives one shared HTTP
|
|
9
|
+
* request implementation. That transport mirrors `@objectstack/connector-rest`
|
|
10
|
+
* (build URL from base+path+query, apply static auth, JSON-encode the body,
|
|
11
|
+
* normalise the response to `{ status, ok, body }`) — kept inline so this package
|
|
12
|
+
* stays self-contained, depending only on `@objectstack/core` + `@objectstack/spec`
|
|
13
|
+
* like its sibling connectors. The output is an ordinary `type: 'api'` connector,
|
|
14
|
+
* registered via `engine.registerConnector(def, handlers)` exactly like a
|
|
15
|
+
* hand-written one — the registry, the `connector_action` node, the discovery
|
|
16
|
+
* route, and the Studio palette never know it came from OpenAPI.
|
|
17
|
+
*
|
|
18
|
+
* Open-source scope: **static** auth only (`none` / `api-key` / `basic` /
|
|
19
|
+
* `bearer`), with credentials supplied by the caller. Managed OAuth2, credential
|
|
20
|
+
* vaulting, and per-tenant lifecycle are the enterprise tier (ADR-0015 / 0022).
|
|
21
|
+
*/
|
|
22
|
+
/** Static auth understood by the generated connector (the open-source subset). */
|
|
23
|
+
type RestAuth = Extract<Connector['authentication'], {
|
|
24
|
+
type: 'none' | 'api-key' | 'basic' | 'bearer';
|
|
25
|
+
}>;
|
|
26
|
+
/** Handler signature accepted by the connector registry (ADR-0018 §Addendum). */
|
|
27
|
+
type ConnectorHandler = (input: Record<string, unknown>, ctx: unknown) => Promise<Record<string, unknown>>;
|
|
28
|
+
/** A connector definition paired with its action handlers, ready for registerConnector(). */
|
|
29
|
+
interface OpenApiConnectorBundle {
|
|
30
|
+
def: Connector;
|
|
31
|
+
handlers: Record<string, ConnectorHandler>;
|
|
32
|
+
}
|
|
33
|
+
/** A free-form JSON Schema fragment (matches ConnectorAction input/outputSchema). */
|
|
34
|
+
type JsonSchema = Record<string, unknown>;
|
|
35
|
+
/** Minimal subset of an OpenAPI 3.x document consumed by the generator.
|
|
36
|
+
* The caller is responsible for loading and de-referencing ($ref) the doc. */
|
|
37
|
+
interface OpenApiDocument {
|
|
38
|
+
openapi?: string;
|
|
39
|
+
info?: {
|
|
40
|
+
title?: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
version?: string;
|
|
43
|
+
};
|
|
44
|
+
servers?: {
|
|
45
|
+
url: string;
|
|
46
|
+
}[];
|
|
47
|
+
paths?: Record<string, OpenApiPathItem>;
|
|
48
|
+
components?: {
|
|
49
|
+
securitySchemes?: Record<string, OpenApiSecurityScheme>;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
interface OpenApiPathItem {
|
|
53
|
+
[method: string]: OpenApiOperation | unknown;
|
|
54
|
+
}
|
|
55
|
+
interface OpenApiOperation {
|
|
56
|
+
operationId?: string;
|
|
57
|
+
summary?: string;
|
|
58
|
+
description?: string;
|
|
59
|
+
tags?: string[];
|
|
60
|
+
parameters?: OpenApiParameter[];
|
|
61
|
+
requestBody?: OpenApiRequestBody;
|
|
62
|
+
responses?: Record<string, OpenApiResponse>;
|
|
63
|
+
}
|
|
64
|
+
interface OpenApiParameter {
|
|
65
|
+
name: string;
|
|
66
|
+
in: 'path' | 'query' | 'header' | 'cookie';
|
|
67
|
+
required?: boolean;
|
|
68
|
+
description?: string;
|
|
69
|
+
schema?: JsonSchema;
|
|
70
|
+
}
|
|
71
|
+
interface OpenApiRequestBody {
|
|
72
|
+
required?: boolean;
|
|
73
|
+
description?: string;
|
|
74
|
+
content?: Record<string, {
|
|
75
|
+
schema?: JsonSchema;
|
|
76
|
+
}>;
|
|
77
|
+
}
|
|
78
|
+
interface OpenApiResponse {
|
|
79
|
+
description?: string;
|
|
80
|
+
content?: Record<string, {
|
|
81
|
+
schema?: JsonSchema;
|
|
82
|
+
}>;
|
|
83
|
+
}
|
|
84
|
+
interface OpenApiSecurityScheme {
|
|
85
|
+
type: 'apiKey' | 'http' | 'oauth2' | 'openIdConnect';
|
|
86
|
+
name?: string;
|
|
87
|
+
in?: 'header' | 'query' | 'cookie';
|
|
88
|
+
scheme?: string;
|
|
89
|
+
}
|
|
90
|
+
/** Flattened view of a single operation, passed to the `include` predicate. */
|
|
91
|
+
interface OperationInfo {
|
|
92
|
+
operationId?: string;
|
|
93
|
+
method: string;
|
|
94
|
+
path: string;
|
|
95
|
+
tags?: string[];
|
|
96
|
+
summary?: string;
|
|
97
|
+
description?: string;
|
|
98
|
+
}
|
|
99
|
+
/** Configuration for {@link createOpenApiConnector}. */
|
|
100
|
+
interface OpenApiConnectorConfig {
|
|
101
|
+
/** Connector machine name (snake_case). Defaults to a slug of info.title. */
|
|
102
|
+
name?: string;
|
|
103
|
+
/** Human-friendly label. Defaults to info.title (then name). */
|
|
104
|
+
label?: string;
|
|
105
|
+
/** Description. Defaults to info.description. */
|
|
106
|
+
description?: string;
|
|
107
|
+
/** Icon identifier for the Studio palette. Defaults to `globe`. */
|
|
108
|
+
icon?: string;
|
|
109
|
+
/** The parsed OpenAPI 3.x document (caller loads/derefs it). */
|
|
110
|
+
document: OpenApiDocument;
|
|
111
|
+
/** Override the base URL (else servers[0].url). */
|
|
112
|
+
baseUrl?: string;
|
|
113
|
+
/** Static auth with credentials. Defaults to `{ type: 'none' }`. */
|
|
114
|
+
auth?: RestAuth;
|
|
115
|
+
/** Headers merged into every request (request-level headers win). */
|
|
116
|
+
defaultHeaders?: Record<string, string>;
|
|
117
|
+
/** Only include operations for which this predicate returns true (allowlist). */
|
|
118
|
+
include?: (op: OperationInfo) => boolean;
|
|
119
|
+
/** Injected fetch implementation (defaults to global `fetch`). */
|
|
120
|
+
fetchImpl?: typeof fetch;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Build an OpenAPI connector definition and its handler map.
|
|
124
|
+
*
|
|
125
|
+
* @returns the `Connector` definition (`def`) and a `handlers` record keyed by
|
|
126
|
+
* action key, suitable for `engine.registerConnector(def, handlers)`.
|
|
127
|
+
*/
|
|
128
|
+
declare function createOpenApiConnector(config: OpenApiConnectorConfig): OpenApiConnectorBundle;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Minimal surface of the automation engine this helper depends on — the
|
|
132
|
+
* connector registry from ADR-0018 §Addendum. Kept structural so callers need
|
|
133
|
+
* no runtime dependency on `@objectstack/service-automation` (mirrors
|
|
134
|
+
* connector-rest / connector-mcp).
|
|
135
|
+
*/
|
|
136
|
+
interface ConnectorRegistrySurface {
|
|
137
|
+
registerConnector(def: Connector, handlers: Record<string, (input: Record<string, unknown>, ctx: unknown) => Promise<Record<string, unknown>>>): void;
|
|
138
|
+
unregisterConnector(name: string): void;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Generate an OpenAPI-backed connector and register it on the engine's connector
|
|
142
|
+
* registry so the baseline `connector_action` node can dispatch to the generated
|
|
143
|
+
* actions (ADR-0023). Returns the registered connector name.
|
|
144
|
+
*/
|
|
145
|
+
declare function registerOpenApiConnector(registry: ConnectorRegistrySurface, config: OpenApiConnectorConfig): string;
|
|
146
|
+
|
|
147
|
+
export { type ConnectorRegistrySurface, type JsonSchema, type OpenApiConnectorBundle, type OpenApiConnectorConfig, type OpenApiDocument, type OpenApiOperation, type OpenApiParameter, type OpenApiPathItem, type OpenApiRequestBody, type OpenApiResponse, type OpenApiSecurityScheme, type OperationInfo, type RestAuth, createOpenApiConnector, registerOpenApiConnector };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
createOpenApiConnector: () => createOpenApiConnector,
|
|
24
|
+
registerOpenApiConnector: () => registerOpenApiConnector
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/openapi-connector.ts
|
|
29
|
+
var HTTP_METHODS = ["get", "put", "post", "delete", "patch", "options", "head", "trace"];
|
|
30
|
+
function createOpenApiConnector(config) {
|
|
31
|
+
const { document, include } = config;
|
|
32
|
+
const auth = config.auth ?? { type: "none" };
|
|
33
|
+
const doFetch = config.fetchImpl ?? fetch;
|
|
34
|
+
const name = config.name ?? slug(document.info?.title ?? "openapi_connector");
|
|
35
|
+
const label = config.label ?? document.info?.title ?? titleize(name);
|
|
36
|
+
const description = config.description ?? document.info?.description;
|
|
37
|
+
const baseUrl = config.baseUrl ?? document.servers?.[0]?.url;
|
|
38
|
+
if (!baseUrl) {
|
|
39
|
+
throw new Error("createOpenApiConnector: no base URL \u2014 provide config.baseUrl or document.servers[0].url");
|
|
40
|
+
}
|
|
41
|
+
async function request(input) {
|
|
42
|
+
const method = input.method.toUpperCase();
|
|
43
|
+
const headers = { ...config.defaultHeaders, ...input.headers };
|
|
44
|
+
const query = { ...input.query };
|
|
45
|
+
applyAuth(auth, headers, query);
|
|
46
|
+
const url = buildUrl(baseUrl, input.path, query);
|
|
47
|
+
const hasBody = input.body !== void 0 && method !== "GET" && method !== "HEAD";
|
|
48
|
+
if (hasBody && headers["Content-Type"] === void 0 && headers["content-type"] === void 0) {
|
|
49
|
+
headers["Content-Type"] = "application/json";
|
|
50
|
+
}
|
|
51
|
+
const response = await doFetch(url, {
|
|
52
|
+
method,
|
|
53
|
+
headers,
|
|
54
|
+
body: hasBody ? JSON.stringify(input.body) : void 0
|
|
55
|
+
});
|
|
56
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
57
|
+
const parsed = contentType.includes("application/json") ? await response.json() : await response.text();
|
|
58
|
+
return { status: response.status, ok: response.ok, body: parsed };
|
|
59
|
+
}
|
|
60
|
+
const actions = [];
|
|
61
|
+
const handlers = {};
|
|
62
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
63
|
+
for (const op of collectOperations(document)) {
|
|
64
|
+
if (include && !include(toInfo(op))) continue;
|
|
65
|
+
const key = uniqueKey(op.operationId ?? slug(`${op.method}_${op.path}`), seenKeys);
|
|
66
|
+
actions.push({
|
|
67
|
+
key,
|
|
68
|
+
label: op.summary ?? titleize(key),
|
|
69
|
+
description: op.description,
|
|
70
|
+
inputSchema: buildInputSchema(op),
|
|
71
|
+
outputSchema: buildOutputSchema(op)
|
|
72
|
+
});
|
|
73
|
+
handlers[key] = async (input) => {
|
|
74
|
+
const req = input;
|
|
75
|
+
return request({
|
|
76
|
+
method: op.method,
|
|
77
|
+
path: interpolatePath(op.path, asRecord(req.path)),
|
|
78
|
+
query: stringifyValues(asRecord(req.query)),
|
|
79
|
+
headers: stringifyValues(asRecord(req.header)),
|
|
80
|
+
body: req.body
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const def = {
|
|
85
|
+
name,
|
|
86
|
+
label,
|
|
87
|
+
type: "api",
|
|
88
|
+
description,
|
|
89
|
+
icon: config.icon ?? "globe",
|
|
90
|
+
authentication: auth,
|
|
91
|
+
// Defaulted by ConnectorSchema; set explicitly so the literal satisfies
|
|
92
|
+
// the (post-parse) Connector output type (mirrors connector-rest/mcp).
|
|
93
|
+
status: "active",
|
|
94
|
+
enabled: true,
|
|
95
|
+
connectionTimeoutMs: 3e4,
|
|
96
|
+
requestTimeoutMs: 3e4,
|
|
97
|
+
actions
|
|
98
|
+
};
|
|
99
|
+
return { def, handlers };
|
|
100
|
+
}
|
|
101
|
+
function collectOperations(doc) {
|
|
102
|
+
const ops = [];
|
|
103
|
+
for (const [path, item] of Object.entries(doc.paths ?? {})) {
|
|
104
|
+
if (!item || typeof item !== "object") continue;
|
|
105
|
+
const record = item;
|
|
106
|
+
for (const method of HTTP_METHODS) {
|
|
107
|
+
const operation = record[method];
|
|
108
|
+
if (!operation || typeof operation !== "object") continue;
|
|
109
|
+
ops.push({ ...operation, method, path });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return ops;
|
|
113
|
+
}
|
|
114
|
+
function toInfo(op) {
|
|
115
|
+
return {
|
|
116
|
+
operationId: op.operationId,
|
|
117
|
+
method: op.method,
|
|
118
|
+
path: op.path,
|
|
119
|
+
tags: op.tags,
|
|
120
|
+
summary: op.summary,
|
|
121
|
+
description: op.description
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function buildInputSchema(op) {
|
|
125
|
+
const sections = {
|
|
126
|
+
path: { props: {}, required: [] },
|
|
127
|
+
query: { props: {}, required: [] },
|
|
128
|
+
header: { props: {}, required: [] }
|
|
129
|
+
};
|
|
130
|
+
for (const p of op.parameters ?? []) {
|
|
131
|
+
if (!p || typeof p !== "object" || "$ref" in p) continue;
|
|
132
|
+
if (p.in !== "path" && p.in !== "query" && p.in !== "header") continue;
|
|
133
|
+
const sec = sections[p.in];
|
|
134
|
+
sec.props[p.name] = p.schema ?? (p.description ? { type: "string", description: p.description } : { type: "string" });
|
|
135
|
+
if (p.required) sec.required.push(p.name);
|
|
136
|
+
}
|
|
137
|
+
const properties = {};
|
|
138
|
+
const required = [];
|
|
139
|
+
for (const where of ["path", "query", "header"]) {
|
|
140
|
+
const sec = sections[where];
|
|
141
|
+
if (Object.keys(sec.props).length === 0) continue;
|
|
142
|
+
const schema2 = { type: "object", properties: sec.props };
|
|
143
|
+
if (sec.required.length) schema2.required = sec.required;
|
|
144
|
+
properties[where] = schema2;
|
|
145
|
+
if (where === "path" || sec.required.length) required.push(where);
|
|
146
|
+
}
|
|
147
|
+
const bodySchema = extractRequestBodySchema(op.requestBody);
|
|
148
|
+
if (bodySchema) {
|
|
149
|
+
properties.body = bodySchema;
|
|
150
|
+
if (op.requestBody && !("$ref" in op.requestBody) && op.requestBody.required) required.push("body");
|
|
151
|
+
}
|
|
152
|
+
if (Object.keys(properties).length === 0) return void 0;
|
|
153
|
+
const schema = { type: "object", properties };
|
|
154
|
+
if (required.length) schema.required = required;
|
|
155
|
+
return schema;
|
|
156
|
+
}
|
|
157
|
+
function buildOutputSchema(op) {
|
|
158
|
+
const responses = op.responses;
|
|
159
|
+
if (!responses) return void 0;
|
|
160
|
+
let code;
|
|
161
|
+
if (responses["200"]) code = "200";
|
|
162
|
+
else code = Object.keys(responses).find((c) => /^2\d\d$/.test(c));
|
|
163
|
+
if (!code && responses["default"]) code = "default";
|
|
164
|
+
if (!code) return void 0;
|
|
165
|
+
const resp = responses[code];
|
|
166
|
+
if (!resp || typeof resp !== "object" || "$ref" in resp) return void 0;
|
|
167
|
+
return pickJsonSchema(resp.content);
|
|
168
|
+
}
|
|
169
|
+
function extractRequestBodySchema(rb) {
|
|
170
|
+
if (!rb || typeof rb !== "object" || "$ref" in rb) return void 0;
|
|
171
|
+
return pickJsonSchema(rb.content);
|
|
172
|
+
}
|
|
173
|
+
function pickJsonSchema(content) {
|
|
174
|
+
if (!content) return void 0;
|
|
175
|
+
const chosen = content["application/json"] ?? Object.values(content)[0];
|
|
176
|
+
return chosen?.schema;
|
|
177
|
+
}
|
|
178
|
+
function buildUrl(baseUrl, path, query) {
|
|
179
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
180
|
+
const suffix = path ? path.startsWith("/") ? path : `/${path}` : "";
|
|
181
|
+
const url = new URL(base + suffix);
|
|
182
|
+
for (const [key, value] of Object.entries(query)) {
|
|
183
|
+
if (value !== void 0 && value !== null) url.searchParams.set(key, String(value));
|
|
184
|
+
}
|
|
185
|
+
return url.toString();
|
|
186
|
+
}
|
|
187
|
+
function applyAuth(auth, headers, query) {
|
|
188
|
+
switch (auth.type) {
|
|
189
|
+
case "none":
|
|
190
|
+
return;
|
|
191
|
+
case "bearer":
|
|
192
|
+
headers["Authorization"] = `Bearer ${auth.token}`;
|
|
193
|
+
return;
|
|
194
|
+
case "basic": {
|
|
195
|
+
const encoded = Buffer.from(`${auth.username}:${auth.password}`).toString("base64");
|
|
196
|
+
headers["Authorization"] = `Basic ${encoded}`;
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
case "api-key":
|
|
200
|
+
if (auth.paramName) query[auth.paramName] = auth.key;
|
|
201
|
+
else headers[auth.headerName ?? "X-API-Key"] = auth.key;
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function interpolatePath(template, pathParams) {
|
|
206
|
+
return template.replace(/\{([^}]+)\}/g, (_match, key) => {
|
|
207
|
+
const value = pathParams[key];
|
|
208
|
+
return value === void 0 || value === null ? `{${key}}` : encodeURIComponent(String(value));
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function stringifyValues(rec) {
|
|
212
|
+
const out = {};
|
|
213
|
+
for (const [k, v] of Object.entries(rec)) {
|
|
214
|
+
if (v === void 0 || v === null) continue;
|
|
215
|
+
out[k] = String(v);
|
|
216
|
+
}
|
|
217
|
+
return out;
|
|
218
|
+
}
|
|
219
|
+
function asRecord(v) {
|
|
220
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
221
|
+
}
|
|
222
|
+
function uniqueKey(base, seen) {
|
|
223
|
+
let candidate = base;
|
|
224
|
+
if (seen.has(candidate)) {
|
|
225
|
+
let i = 2;
|
|
226
|
+
while (seen.has(`${base}_${i}`)) i++;
|
|
227
|
+
candidate = `${base}_${i}`;
|
|
228
|
+
}
|
|
229
|
+
seen.add(candidate);
|
|
230
|
+
return candidate;
|
|
231
|
+
}
|
|
232
|
+
function slug(s) {
|
|
233
|
+
const out = s.normalize("NFKD").replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
234
|
+
if (!out) return "connector";
|
|
235
|
+
return /^[a-z_]/.test(out) ? out : `op_${out}`;
|
|
236
|
+
}
|
|
237
|
+
function titleize(name) {
|
|
238
|
+
return name.split("_").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/connector-openapi-plugin.ts
|
|
242
|
+
function registerOpenApiConnector(registry, config) {
|
|
243
|
+
const { def, handlers } = createOpenApiConnector(config);
|
|
244
|
+
registry.registerConnector(def, handlers);
|
|
245
|
+
return def.name;
|
|
246
|
+
}
|
|
247
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
248
|
+
0 && (module.exports = {
|
|
249
|
+
createOpenApiConnector,
|
|
250
|
+
registerOpenApiConnector
|
|
251
|
+
});
|
|
252
|
+
//# sourceMappingURL=index.js.map
|