@valon-technologies/gestalt 0.0.1-alpha.1 → 0.0.1-alpha.11
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/README.md +8 -26
- package/gen/v1/{auth_pb.ts → authentication_pb.ts} +51 -17
- package/gen/v1/authorization_pb.ts +857 -0
- package/gen/v1/cache_pb.ts +32 -0
- package/gen/v1/datastore_pb.ts +62 -0
- package/gen/v1/plugin_pb.ts +279 -18
- package/gen/v1/runtime_pb.ts +27 -3
- package/gen/v1/s3_pb.ts +39 -0
- package/gen/v1/secrets_pb.ts +6 -0
- package/gen/v1/workflow_pb.ts +1372 -0
- package/package.json +12 -10
- package/src/api.ts +56 -0
- package/src/auth.ts +67 -16
- package/src/build.ts +37 -21
- package/src/cache.ts +32 -0
- package/src/catalog.ts +27 -0
- package/src/index.ts +105 -18
- package/src/indexeddb.ts +166 -0
- package/src/invoker.ts +160 -0
- package/src/manifest-metadata.ts +101 -0
- package/src/plugin.ts +255 -38
- package/src/provider-kind.ts +107 -0
- package/src/provider.ts +32 -1
- package/src/runtime.ts +245 -219
- package/src/s3.ts +135 -20
- package/src/schema.ts +46 -0
- package/src/secrets.ts +12 -0
- package/src/target.ts +58 -60
- package/src/workflow-manager.ts +131 -0
- package/src/workflow.ts +479 -0
- package/tsconfig.json +1 -0
package/src/invoker.ts
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { connect } from "node:net";
|
|
2
|
+
|
|
3
|
+
import type { JsonObject, JsonValue } from "@bufbuild/protobuf";
|
|
4
|
+
import { createClient, type Client } from "@connectrpc/connect";
|
|
5
|
+
import { createGrpcTransport } from "@connectrpc/connect-node";
|
|
6
|
+
|
|
7
|
+
import { PluginInvoker as PluginInvokerService } from "../gen/v1/plugin_pb.ts";
|
|
8
|
+
import type { OperationResult, Request } from "./api.ts";
|
|
9
|
+
|
|
10
|
+
export const ENV_PLUGIN_INVOKER_SOCKET = "GESTALT_PLUGIN_INVOKER_SOCKET";
|
|
11
|
+
|
|
12
|
+
export interface PluginInvokeOptions {
|
|
13
|
+
connection?: string;
|
|
14
|
+
instance?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PluginInvocationGrant {
|
|
18
|
+
plugin: string;
|
|
19
|
+
operations?: string[];
|
|
20
|
+
surfaces?: string[];
|
|
21
|
+
allOperations?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface PluginGraphQLInvokeOptions extends PluginInvokeOptions {
|
|
25
|
+
variables?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class PluginInvoker {
|
|
29
|
+
private readonly client: Client<typeof PluginInvokerService>;
|
|
30
|
+
private readonly invocationToken: string;
|
|
31
|
+
|
|
32
|
+
constructor(request: Request);
|
|
33
|
+
constructor(invocationToken: string);
|
|
34
|
+
constructor(requestOrToken: Request | string) {
|
|
35
|
+
this.invocationToken = normalizeInvocationToken(requestOrToken);
|
|
36
|
+
|
|
37
|
+
const socketPath = process.env[ENV_PLUGIN_INVOKER_SOCKET];
|
|
38
|
+
if (!socketPath) {
|
|
39
|
+
throw new Error(`plugin invoker: ${ENV_PLUGIN_INVOKER_SOCKET} is not set`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const transport = createGrpcTransport({
|
|
43
|
+
baseUrl: "http://localhost",
|
|
44
|
+
nodeOptions: {
|
|
45
|
+
createConnection: () => connect(socketPath),
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
this.client = createClient(PluginInvokerService, transport);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async invoke(
|
|
52
|
+
plugin: string,
|
|
53
|
+
operation: string,
|
|
54
|
+
params: Record<string, unknown> = {},
|
|
55
|
+
options?: PluginInvokeOptions,
|
|
56
|
+
): Promise<OperationResult> {
|
|
57
|
+
const response = await this.client.invoke({
|
|
58
|
+
invocationToken: this.invocationToken,
|
|
59
|
+
plugin,
|
|
60
|
+
operation,
|
|
61
|
+
params: toJsonObject(params),
|
|
62
|
+
connection: options?.connection ?? "",
|
|
63
|
+
instance: options?.instance ?? "",
|
|
64
|
+
});
|
|
65
|
+
return {
|
|
66
|
+
status: response.status,
|
|
67
|
+
body: response.body,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async invokeGraphQL(
|
|
72
|
+
plugin: string,
|
|
73
|
+
document: string,
|
|
74
|
+
options?: PluginGraphQLInvokeOptions,
|
|
75
|
+
): Promise<OperationResult> {
|
|
76
|
+
const trimmedDocument = document.trim();
|
|
77
|
+
if (!trimmedDocument) {
|
|
78
|
+
throw new Error("plugin invoker: graphql document is required");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const response = await this.client.invokeGraphQL({
|
|
82
|
+
invocationToken: this.invocationToken,
|
|
83
|
+
plugin,
|
|
84
|
+
document: trimmedDocument,
|
|
85
|
+
...(options?.variables
|
|
86
|
+
? { variables: toJsonObject(options.variables) }
|
|
87
|
+
: {}),
|
|
88
|
+
connection: options?.connection ?? "",
|
|
89
|
+
instance: options?.instance ?? "",
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
status: response.status,
|
|
93
|
+
body: response.body,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async exchangeInvocationToken(options?: {
|
|
98
|
+
grants?: PluginInvocationGrant[];
|
|
99
|
+
ttlSeconds?: number;
|
|
100
|
+
}): Promise<string> {
|
|
101
|
+
const response = await this.client.exchangeInvocationToken({
|
|
102
|
+
parentInvocationToken: this.invocationToken,
|
|
103
|
+
grants: (options?.grants ?? [])
|
|
104
|
+
.map((grant) => ({
|
|
105
|
+
plugin: grant.plugin.trim(),
|
|
106
|
+
operations: (grant.operations ?? [])
|
|
107
|
+
.map((operation) => operation.trim())
|
|
108
|
+
.filter(Boolean),
|
|
109
|
+
surfaces: (grant.surfaces ?? [])
|
|
110
|
+
.map((surface) => surface.trim().toLowerCase())
|
|
111
|
+
.filter(Boolean),
|
|
112
|
+
allOperations: grant.allOperations ?? false,
|
|
113
|
+
}))
|
|
114
|
+
.filter((grant) => grant.plugin.length > 0),
|
|
115
|
+
ttlSeconds: BigInt(Math.max(0, options?.ttlSeconds ?? 0)),
|
|
116
|
+
});
|
|
117
|
+
return response.invocationToken;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function normalizeInvocationToken(requestOrToken: Request | string): string {
|
|
122
|
+
const invocationToken =
|
|
123
|
+
typeof requestOrToken === "string"
|
|
124
|
+
? requestOrToken
|
|
125
|
+
: requestOrToken.invocationToken;
|
|
126
|
+
const trimmed = invocationToken.trim();
|
|
127
|
+
if (!trimmed) {
|
|
128
|
+
throw new Error("plugin invoker: invocation token is not available");
|
|
129
|
+
}
|
|
130
|
+
return trimmed;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function toJsonObject(params: Record<string, unknown>): JsonObject {
|
|
134
|
+
const output: JsonObject = {};
|
|
135
|
+
for (const [key, value] of Object.entries(params ?? {})) {
|
|
136
|
+
if (value === undefined) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
output[key] = toJsonValue(value);
|
|
140
|
+
}
|
|
141
|
+
return output;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function toJsonValue(value: unknown): JsonValue {
|
|
145
|
+
if (
|
|
146
|
+
value === null ||
|
|
147
|
+
typeof value === "string" ||
|
|
148
|
+
typeof value === "number" ||
|
|
149
|
+
typeof value === "boolean"
|
|
150
|
+
) {
|
|
151
|
+
return value as JsonValue;
|
|
152
|
+
}
|
|
153
|
+
if (Array.isArray(value)) {
|
|
154
|
+
return value.map((entry) => toJsonValue(entry));
|
|
155
|
+
}
|
|
156
|
+
if (typeof value === "object") {
|
|
157
|
+
return toJsonObject(value as Record<string, unknown>);
|
|
158
|
+
}
|
|
159
|
+
throw new Error("plugin invoker: params must be JSON-serializable");
|
|
160
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
import YAML from "yaml";
|
|
4
|
+
|
|
5
|
+
export type HTTPSecuritySchemeType =
|
|
6
|
+
| "slack_signature"
|
|
7
|
+
| "apiKey"
|
|
8
|
+
| "http"
|
|
9
|
+
| "none";
|
|
10
|
+
|
|
11
|
+
export type HTTPIn = "header" | "query";
|
|
12
|
+
|
|
13
|
+
export type HTTPAuthScheme = "basic" | "bearer";
|
|
14
|
+
|
|
15
|
+
export interface HTTPSecretRef {
|
|
16
|
+
env?: string;
|
|
17
|
+
secret?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface HTTPSecurityScheme {
|
|
21
|
+
type?: HTTPSecuritySchemeType;
|
|
22
|
+
description?: string;
|
|
23
|
+
name?: string;
|
|
24
|
+
in?: HTTPIn;
|
|
25
|
+
scheme?: HTTPAuthScheme;
|
|
26
|
+
secret?: HTTPSecretRef;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface HTTPMediaType {}
|
|
30
|
+
|
|
31
|
+
export interface HTTPRequestBody {
|
|
32
|
+
required?: boolean;
|
|
33
|
+
content?: Record<string, HTTPMediaType>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface HTTPAck {
|
|
37
|
+
status?: number;
|
|
38
|
+
headers?: Record<string, string>;
|
|
39
|
+
body?: any;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface HTTPBinding {
|
|
43
|
+
path: string;
|
|
44
|
+
method: string;
|
|
45
|
+
requestBody?: HTTPRequestBody;
|
|
46
|
+
security: string;
|
|
47
|
+
target: string;
|
|
48
|
+
ack?: HTTPAck;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface PluginManifestMetadata {
|
|
52
|
+
securitySchemes?: Record<string, HTTPSecurityScheme>;
|
|
53
|
+
http?: Record<string, HTTPBinding>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function hasPluginManifestMetadata(
|
|
57
|
+
metadata: PluginManifestMetadata | null | undefined,
|
|
58
|
+
): boolean {
|
|
59
|
+
return !!(
|
|
60
|
+
metadata &&
|
|
61
|
+
((metadata.securitySchemes &&
|
|
62
|
+
Object.keys(metadata.securitySchemes).length > 0) ||
|
|
63
|
+
(metadata.http && Object.keys(metadata.http).length > 0))
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function manifestMetadataToYaml(
|
|
68
|
+
metadata: PluginManifestMetadata | Record<string, unknown>,
|
|
69
|
+
): string {
|
|
70
|
+
return YAML.stringify(toManifestMetadataJsonObject(metadata));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function writeManifestMetadataYaml(
|
|
74
|
+
path: string,
|
|
75
|
+
metadata: PluginManifestMetadata | Record<string, unknown>,
|
|
76
|
+
): void {
|
|
77
|
+
writeFileSync(path, manifestMetadataToYaml(metadata), "utf8");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function toManifestMetadataJsonObject(
|
|
81
|
+
metadata: PluginManifestMetadata | Record<string, unknown>,
|
|
82
|
+
): Record<string, unknown> {
|
|
83
|
+
if (!("securitySchemes" in metadata) && !("http" in metadata)) {
|
|
84
|
+
return {
|
|
85
|
+
...metadata,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const typedMetadata = metadata as PluginManifestMetadata;
|
|
90
|
+
const output: Record<string, unknown> = {};
|
|
91
|
+
if (
|
|
92
|
+
typedMetadata.securitySchemes &&
|
|
93
|
+
Object.keys(typedMetadata.securitySchemes).length > 0
|
|
94
|
+
) {
|
|
95
|
+
output.securitySchemes = typedMetadata.securitySchemes;
|
|
96
|
+
}
|
|
97
|
+
if (typedMetadata.http && Object.keys(typedMetadata.http).length > 0) {
|
|
98
|
+
output.http = typedMetadata.http;
|
|
99
|
+
}
|
|
100
|
+
return output;
|
|
101
|
+
}
|
package/src/plugin.ts
CHANGED
|
@@ -7,6 +7,15 @@ import {
|
|
|
7
7
|
schemaToParameters,
|
|
8
8
|
writeCatalogYaml,
|
|
9
9
|
} from "./catalog.ts";
|
|
10
|
+
import {
|
|
11
|
+
type HTTPAck,
|
|
12
|
+
type HTTPBinding,
|
|
13
|
+
type HTTPRequestBody,
|
|
14
|
+
type HTTPSecurityScheme,
|
|
15
|
+
type PluginManifestMetadata,
|
|
16
|
+
hasPluginManifestMetadata,
|
|
17
|
+
writeManifestMetadataYaml,
|
|
18
|
+
} from "./manifest-metadata.ts";
|
|
10
19
|
import {
|
|
11
20
|
errorMessage,
|
|
12
21
|
type MaybePromise,
|
|
@@ -18,13 +27,18 @@ import {
|
|
|
18
27
|
import { RuntimeProvider, type RuntimeProviderOptions } from "./provider.ts";
|
|
19
28
|
import type { Schema } from "./schema.ts";
|
|
20
29
|
|
|
30
|
+
/**
|
|
31
|
+
* How a plugin provider expects to authenticate or connect.
|
|
32
|
+
*/
|
|
21
33
|
export type ConnectionMode =
|
|
22
34
|
| "unspecified"
|
|
23
35
|
| "none"
|
|
24
36
|
| "user"
|
|
25
|
-
| "identity"
|
|
26
|
-
| "either";
|
|
37
|
+
| "identity";
|
|
27
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Metadata for a single connection parameter exposed by a provider.
|
|
41
|
+
*/
|
|
28
42
|
export interface ConnectionParamDefinition {
|
|
29
43
|
required?: boolean;
|
|
30
44
|
description?: string;
|
|
@@ -33,6 +47,9 @@ export interface ConnectionParamDefinition {
|
|
|
33
47
|
field?: string;
|
|
34
48
|
}
|
|
35
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Operation definition accepted by {@link operation} and {@link definePlugin}.
|
|
52
|
+
*/
|
|
36
53
|
export interface OperationOptions<In, Out> {
|
|
37
54
|
id: string;
|
|
38
55
|
method?: string;
|
|
@@ -47,27 +64,43 @@ export interface OperationOptions<In, Out> {
|
|
|
47
64
|
handler: (input: In, request: Request) => MaybePromise<Out | Response<Out>>;
|
|
48
65
|
}
|
|
49
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Normalized plugin operation definition.
|
|
69
|
+
*/
|
|
50
70
|
export interface OperationDefinition<In, Out> extends OperationOptions<
|
|
51
71
|
In,
|
|
52
72
|
Out
|
|
53
73
|
> {}
|
|
54
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Session-specific catalog payload returned by a provider at runtime.
|
|
77
|
+
*/
|
|
55
78
|
export type SessionCatalog = Catalog | Record<string, unknown>;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Callback used to resolve a catalog for an authenticated request context.
|
|
82
|
+
*/
|
|
56
83
|
export type SessionCatalogHandler = (
|
|
57
84
|
request: Request,
|
|
58
85
|
) => MaybePromise<SessionCatalog | null | undefined>;
|
|
59
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Runtime hooks required to implement a plugin provider.
|
|
89
|
+
*/
|
|
60
90
|
export interface PluginDefinitionOptions extends RuntimeProviderOptions {
|
|
61
91
|
connectionMode?: ConnectionMode;
|
|
62
92
|
authTypes?: string[];
|
|
63
93
|
connectionParams?: Record<string, ConnectionParamDefinition>;
|
|
94
|
+
securitySchemes?: Record<string, HTTPSecurityScheme>;
|
|
95
|
+
http?: Record<string, HTTPBinding>;
|
|
64
96
|
iconSvg?: string;
|
|
65
97
|
operations: Array<OperationDefinition<any, any>>;
|
|
66
98
|
sessionCatalog?: SessionCatalogHandler;
|
|
67
99
|
}
|
|
68
100
|
|
|
69
|
-
|
|
70
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Normalizes a plugin operation definition.
|
|
103
|
+
*/
|
|
71
104
|
export function operation<In, Out>(
|
|
72
105
|
options: OperationOptions<In, Out>,
|
|
73
106
|
): OperationDefinition<In, Out> {
|
|
@@ -82,18 +115,41 @@ export function operation<In, Out>(
|
|
|
82
115
|
};
|
|
83
116
|
}
|
|
84
117
|
|
|
85
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Plugin provider implementation consumed by the Gestalt runtime.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* import { definePlugin, ok, operation, s } from "@valon-technologies/gestalt";
|
|
124
|
+
*
|
|
125
|
+
* export const plugin = definePlugin({
|
|
126
|
+
* displayName: "Example Provider",
|
|
127
|
+
* operations: [
|
|
128
|
+
* operation({
|
|
129
|
+
* id: "ping",
|
|
130
|
+
* method: "GET",
|
|
131
|
+
* readOnly: true,
|
|
132
|
+
* input: s.object({ name: s.string({ default: "World" }) }),
|
|
133
|
+
* output: s.object({ message: s.string() }),
|
|
134
|
+
* async handler(input) {
|
|
135
|
+
* return ok({ message: `Hello, ${input.name}` });
|
|
136
|
+
* },
|
|
137
|
+
* }),
|
|
138
|
+
* ],
|
|
139
|
+
* });
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
export class PluginProvider extends RuntimeProvider {
|
|
86
143
|
readonly kind = "integration" as const;
|
|
87
144
|
readonly iconSvg: string;
|
|
88
145
|
readonly connectionMode: ConnectionMode;
|
|
89
146
|
readonly authTypes: string[];
|
|
90
147
|
readonly connectionParams: Record<string, ConnectionParamDefinition>;
|
|
148
|
+
readonly securitySchemes: Record<string, HTTPSecurityScheme>;
|
|
149
|
+
readonly http: Record<string, HTTPBinding>;
|
|
91
150
|
|
|
92
151
|
private readonly sessionCatalogHandler: SessionCatalogHandler | undefined;
|
|
93
|
-
private readonly operations = new Map<
|
|
94
|
-
string,
|
|
95
|
-
OperationDefinition<any, any>
|
|
96
|
-
>();
|
|
152
|
+
private readonly operations = new Map<string, OperationDefinition<any, any>>();
|
|
97
153
|
|
|
98
154
|
constructor(options: PluginDefinitionOptions) {
|
|
99
155
|
super(options);
|
|
@@ -101,38 +157,41 @@ export class IntegrationProvider extends RuntimeProvider {
|
|
|
101
157
|
this.connectionMode = options.connectionMode ?? "unspecified";
|
|
102
158
|
this.authTypes = [...(options.authTypes ?? [])];
|
|
103
159
|
this.connectionParams = normalizeConnectionParams(options.connectionParams);
|
|
160
|
+
this.securitySchemes = normalizeHTTPSecuritySchemes(options.securitySchemes);
|
|
161
|
+
this.http = normalizeHTTPBindings(options.http);
|
|
104
162
|
this.sessionCatalogHandler = options.sessionCatalog;
|
|
105
163
|
|
|
106
|
-
for (const
|
|
107
|
-
const
|
|
108
|
-
if (!id) {
|
|
164
|
+
for (const rawEntry of options.operations) {
|
|
165
|
+
const entry = operation(rawEntry);
|
|
166
|
+
if (!entry.id) {
|
|
109
167
|
throw new Error("operation id is required");
|
|
110
168
|
}
|
|
111
|
-
if (this.operations.has(id)) {
|
|
112
|
-
throw new Error(`duplicate operation id ${JSON.stringify(id)}`);
|
|
169
|
+
if (this.operations.has(entry.id)) {
|
|
170
|
+
throw new Error(`duplicate operation id ${JSON.stringify(entry.id)}`);
|
|
113
171
|
}
|
|
114
|
-
this.operations.set(id,
|
|
115
|
-
...entry,
|
|
116
|
-
id,
|
|
117
|
-
method: normalizeMethod(entry.method),
|
|
118
|
-
title: entry.title?.trim() ?? "",
|
|
119
|
-
description: entry.description?.trim() ?? "",
|
|
120
|
-
allowedRoles: normalizeAllowedRoles(entry.allowedRoles),
|
|
121
|
-
tags: [...(entry.tags ?? [])],
|
|
122
|
-
});
|
|
172
|
+
this.operations.set(entry.id, entry);
|
|
123
173
|
}
|
|
124
174
|
}
|
|
125
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Reports whether the provider exposes a session-specific catalog.
|
|
178
|
+
*/
|
|
126
179
|
supportsSessionCatalog(): boolean {
|
|
127
180
|
return this.sessionCatalogHandler !== undefined;
|
|
128
181
|
}
|
|
129
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Resolves a catalog for the current request context, if configured.
|
|
185
|
+
*/
|
|
130
186
|
async catalogForRequest(
|
|
131
187
|
request: Request,
|
|
132
188
|
): Promise<SessionCatalog | null | undefined> {
|
|
133
189
|
return await this.sessionCatalogHandler?.(request);
|
|
134
190
|
}
|
|
135
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Returns the static catalog emitted during provider startup.
|
|
194
|
+
*/
|
|
136
195
|
staticCatalog(): Catalog {
|
|
137
196
|
const catalog: Catalog = {
|
|
138
197
|
operations: [...this.operations.values()].map<CatalogOperation>(
|
|
@@ -197,14 +256,55 @@ export class IntegrationProvider extends RuntimeProvider {
|
|
|
197
256
|
return catalog;
|
|
198
257
|
}
|
|
199
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Writes the provider's static catalog to disk as YAML.
|
|
261
|
+
*/
|
|
200
262
|
writeCatalog(path: string): void {
|
|
201
263
|
writeCatalogYaml(path, this.staticCatalog());
|
|
202
264
|
}
|
|
203
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Returns the static catalog serialized as JSON.
|
|
268
|
+
*/
|
|
204
269
|
catalogJson(): string {
|
|
205
270
|
return catalogToJson(this.staticCatalog());
|
|
206
271
|
}
|
|
207
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Returns generated manifest-backed HTTP/security metadata for the provider.
|
|
275
|
+
*/
|
|
276
|
+
staticManifestMetadata(): PluginManifestMetadata {
|
|
277
|
+
const metadata: PluginManifestMetadata = {};
|
|
278
|
+
if (Object.keys(this.securitySchemes).length > 0) {
|
|
279
|
+
metadata.securitySchemes = cloneHTTPSecuritySchemes(this.securitySchemes);
|
|
280
|
+
}
|
|
281
|
+
if (Object.keys(this.http).length > 0) {
|
|
282
|
+
metadata.http = cloneHTTPBindings(this.http);
|
|
283
|
+
}
|
|
284
|
+
return metadata;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Reports whether the provider emits manifest metadata in addition to catalog metadata.
|
|
289
|
+
*/
|
|
290
|
+
supportsManifestMetadata(): boolean {
|
|
291
|
+
return hasPluginManifestMetadata(this.staticManifestMetadata());
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Writes generated manifest metadata to disk as YAML.
|
|
296
|
+
*/
|
|
297
|
+
writeManifestMetadata(path: string): void {
|
|
298
|
+
const metadata = this.staticManifestMetadata();
|
|
299
|
+
if (!hasPluginManifestMetadata(metadata)) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
writeManifestMetadataYaml(path, metadata);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Executes an operation against validated input and request metadata.
|
|
307
|
+
*/
|
|
208
308
|
async execute(
|
|
209
309
|
operationId: string,
|
|
210
310
|
params: Record<string, unknown>,
|
|
@@ -244,25 +344,23 @@ export class IntegrationProvider extends RuntimeProvider {
|
|
|
244
344
|
}
|
|
245
345
|
}
|
|
246
346
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
options: PluginDefinitionOptions,
|
|
251
|
-
): IntegrationProvider {
|
|
252
|
-
return new IntegrationProvider(options);
|
|
253
|
-
}
|
|
254
|
-
|
|
347
|
+
/**
|
|
348
|
+
* Creates a plugin provider.
|
|
349
|
+
*/
|
|
255
350
|
export function definePlugin(
|
|
256
351
|
options: PluginDefinitionOptions,
|
|
257
|
-
):
|
|
258
|
-
return new
|
|
352
|
+
): PluginProvider {
|
|
353
|
+
return new PluginProvider(options);
|
|
259
354
|
}
|
|
260
355
|
|
|
261
|
-
|
|
356
|
+
/**
|
|
357
|
+
* Runtime type guard for plugin providers loaded from user modules.
|
|
358
|
+
*/
|
|
359
|
+
export function isPluginProvider(
|
|
262
360
|
value: unknown,
|
|
263
|
-
): value is
|
|
361
|
+
): value is PluginProvider {
|
|
264
362
|
return (
|
|
265
|
-
value instanceof
|
|
363
|
+
value instanceof PluginProvider ||
|
|
266
364
|
(typeof value === "object" &&
|
|
267
365
|
value !== null &&
|
|
268
366
|
"kind" in value &&
|
|
@@ -298,6 +396,121 @@ function normalizeConnectionParams(
|
|
|
298
396
|
return output;
|
|
299
397
|
}
|
|
300
398
|
|
|
399
|
+
function normalizeHTTPSecuritySchemes(
|
|
400
|
+
input: Record<string, HTTPSecurityScheme> | undefined,
|
|
401
|
+
): Record<string, HTTPSecurityScheme> {
|
|
402
|
+
const output: Record<string, HTTPSecurityScheme> = {};
|
|
403
|
+
for (const [key, value] of Object.entries(input ?? {})) {
|
|
404
|
+
output[key] = cloneHTTPSecurityScheme(value);
|
|
405
|
+
}
|
|
406
|
+
return output;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function cloneHTTPSecuritySchemes(
|
|
410
|
+
input: Record<string, HTTPSecurityScheme>,
|
|
411
|
+
): Record<string, HTTPSecurityScheme> {
|
|
412
|
+
const output: Record<string, HTTPSecurityScheme> = {};
|
|
413
|
+
for (const [key, value] of Object.entries(input)) {
|
|
414
|
+
output[key] = cloneHTTPSecurityScheme(value);
|
|
415
|
+
}
|
|
416
|
+
return output;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function cloneHTTPSecurityScheme(value: HTTPSecurityScheme): HTTPSecurityScheme {
|
|
420
|
+
const output: HTTPSecurityScheme = {};
|
|
421
|
+
if (value.type !== undefined) {
|
|
422
|
+
output.type = value.type;
|
|
423
|
+
}
|
|
424
|
+
if (value.description !== undefined) {
|
|
425
|
+
output.description = value.description;
|
|
426
|
+
}
|
|
427
|
+
if (value.name !== undefined) {
|
|
428
|
+
output.name = value.name;
|
|
429
|
+
}
|
|
430
|
+
if (value.in !== undefined) {
|
|
431
|
+
output.in = value.in;
|
|
432
|
+
}
|
|
433
|
+
if (value.scheme !== undefined) {
|
|
434
|
+
output.scheme = value.scheme;
|
|
435
|
+
}
|
|
436
|
+
if (value.secret) {
|
|
437
|
+
output.secret = {
|
|
438
|
+
...value.secret,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
return output;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function normalizeHTTPBindings(
|
|
445
|
+
input: Record<string, HTTPBinding> | undefined,
|
|
446
|
+
): Record<string, HTTPBinding> {
|
|
447
|
+
const output: Record<string, HTTPBinding> = {};
|
|
448
|
+
for (const [key, value] of Object.entries(input ?? {})) {
|
|
449
|
+
output[key] = cloneHTTPBinding(value);
|
|
450
|
+
}
|
|
451
|
+
return output;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function cloneHTTPBindings(
|
|
455
|
+
input: Record<string, HTTPBinding>,
|
|
456
|
+
): Record<string, HTTPBinding> {
|
|
457
|
+
const output: Record<string, HTTPBinding> = {};
|
|
458
|
+
for (const [key, value] of Object.entries(input)) {
|
|
459
|
+
output[key] = cloneHTTPBinding(value);
|
|
460
|
+
}
|
|
461
|
+
return output;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function cloneHTTPBinding(value: HTTPBinding): HTTPBinding {
|
|
465
|
+
const output: HTTPBinding = {
|
|
466
|
+
path: value.path,
|
|
467
|
+
method: value.method,
|
|
468
|
+
security: value.security,
|
|
469
|
+
target: value.target,
|
|
470
|
+
};
|
|
471
|
+
if (value.requestBody) {
|
|
472
|
+
output.requestBody = cloneHTTPRequestBody(value.requestBody);
|
|
473
|
+
}
|
|
474
|
+
if (value.ack) {
|
|
475
|
+
output.ack = cloneHTTPAck(value.ack);
|
|
476
|
+
}
|
|
477
|
+
return output;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function cloneHTTPRequestBody(value: HTTPRequestBody): HTTPRequestBody {
|
|
481
|
+
const output: HTTPRequestBody = {};
|
|
482
|
+
if (value.required !== undefined) {
|
|
483
|
+
output.required = value.required;
|
|
484
|
+
}
|
|
485
|
+
if (value.content) {
|
|
486
|
+
output.content = {};
|
|
487
|
+
for (const key of Object.keys(value.content)) {
|
|
488
|
+
output.content[key] = {};
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return output;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function cloneHTTPAck(value: HTTPAck): HTTPAck {
|
|
495
|
+
const output: HTTPAck = {};
|
|
496
|
+
if (value.status !== undefined) {
|
|
497
|
+
output.status = value.status;
|
|
498
|
+
}
|
|
499
|
+
if (value.headers) {
|
|
500
|
+
output.headers = {
|
|
501
|
+
...value.headers,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
if (value.body !== undefined) {
|
|
505
|
+
output.body = cloneHTTPBodyValue(value.body);
|
|
506
|
+
}
|
|
507
|
+
return output;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function cloneHTTPBodyValue<T>(value: T): T {
|
|
511
|
+
return structuredClone(value);
|
|
512
|
+
}
|
|
513
|
+
|
|
301
514
|
function isResponse(value: unknown): value is Response<unknown> {
|
|
302
515
|
if (typeof value !== "object" || value === null) {
|
|
303
516
|
return false;
|
|
@@ -353,6 +566,9 @@ function errorResult(status: number, message: string): OperationResult {
|
|
|
353
566
|
};
|
|
354
567
|
}
|
|
355
568
|
|
|
569
|
+
/**
|
|
570
|
+
* Converts a connection mode into the shared protocol enum value.
|
|
571
|
+
*/
|
|
356
572
|
export function connectionModeToProtoValue(mode: ConnectionMode): number {
|
|
357
573
|
switch (mode) {
|
|
358
574
|
case "none":
|
|
@@ -361,14 +577,15 @@ export function connectionModeToProtoValue(mode: ConnectionMode): number {
|
|
|
361
577
|
return 2;
|
|
362
578
|
case "identity":
|
|
363
579
|
return 3;
|
|
364
|
-
case "either":
|
|
365
|
-
return 4;
|
|
366
580
|
case "unspecified":
|
|
367
581
|
default:
|
|
368
582
|
return 0;
|
|
369
583
|
}
|
|
370
584
|
}
|
|
371
585
|
|
|
586
|
+
/**
|
|
587
|
+
* Converts a connection parameter definition into protocol wire metadata.
|
|
588
|
+
*/
|
|
372
589
|
export function connectionParamToProto(value: ConnectionParamDefinition): {
|
|
373
590
|
required?: boolean;
|
|
374
591
|
description?: string;
|