@executor-js/plugin-graphql 1.4.33 → 1.5.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/dist/AddGraphqlSource-P3D3UXRJ.js +179 -0
- package/dist/AddGraphqlSource-P3D3UXRJ.js.map +1 -0
- package/dist/EditGraphqlSource-FVF67CTP.js +38 -0
- package/dist/EditGraphqlSource-FVF67CTP.js.map +1 -0
- package/dist/GraphqlAccountsPanel-GBNFHLFH.js +9 -0
- package/dist/GraphqlAccountsPanel-GBNFHLFH.js.map +1 -0
- package/dist/api/group.d.ts +108 -119
- package/dist/api/handlers.d.ts +9 -7
- package/dist/api/index.d.ts +117 -104
- package/dist/chunk-2Y3J3CVO.js +191 -0
- package/dist/chunk-2Y3J3CVO.js.map +1 -0
- package/dist/chunk-ADQTI2OK.js +1309 -0
- package/dist/chunk-ADQTI2OK.js.map +1 -0
- package/dist/{chunk-UIAHATHP.js → chunk-N5GJE7R6.js} +42 -58
- package/dist/chunk-N5GJE7R6.js.map +1 -0
- package/dist/chunk-VCYDSSIK.js +79 -0
- package/dist/chunk-VCYDSSIK.js.map +1 -0
- package/dist/client.js +7 -7
- package/dist/client.js.map +1 -1
- package/dist/core.js +20 -28
- package/dist/index.js +2 -2
- package/dist/react/AddGraphqlSource.d.ts +1 -1
- package/dist/react/EditGraphqlSource.d.ts +2 -2
- package/dist/react/GraphqlAccountsPanel.d.ts +6 -0
- package/dist/react/GraphqlSignInButton.d.ts +6 -2
- package/dist/react/GraphqlSourceFields.d.ts +2 -2
- package/dist/react/atoms.d.ts +95 -205
- package/dist/react/auth-method-config.d.ts +9 -0
- package/dist/react/client.d.ts +108 -97
- package/dist/react/defaults.d.ts +16 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/react/source-plugin.d.ts +2 -2
- package/dist/sdk/errors.d.ts +8 -6
- package/dist/sdk/index.d.ts +5 -5
- package/dist/sdk/introspect.d.ts +1 -1
- package/dist/sdk/invoke.d.ts +1 -4
- package/dist/sdk/plugin.d.ts +94 -104
- package/dist/sdk/store.d.ts +11 -28
- package/dist/sdk/types.d.ts +110 -72
- package/package.json +5 -4
- package/dist/AddGraphqlSource-CJCUUYNM.js +0 -250
- package/dist/AddGraphqlSource-CJCUUYNM.js.map +0 -1
- package/dist/EditGraphqlSource-YLTPUYW4.js +0 -267
- package/dist/EditGraphqlSource-YLTPUYW4.js.map +0 -1
- package/dist/GraphqlSourceSummary-YWJX6ONU.js +0 -70
- package/dist/GraphqlSourceSummary-YWJX6ONU.js.map +0 -1
- package/dist/chunk-2252RTCE.js +0 -1730
- package/dist/chunk-2252RTCE.js.map +0 -1
- package/dist/chunk-D3FN3ZDA.js +0 -125
- package/dist/chunk-D3FN3ZDA.js.map +0 -1
- package/dist/chunk-M4SJY6CB.js +0 -45
- package/dist/chunk-M4SJY6CB.js.map +0 -1
- package/dist/chunk-UIAHATHP.js.map +0 -1
- package/dist/react/GraphqlSourceSummary.d.ts +0 -5
|
@@ -0,0 +1,1309 @@
|
|
|
1
|
+
import {
|
|
2
|
+
graphqlPresets
|
|
3
|
+
} from "./chunk-D6E4WAYW.js";
|
|
4
|
+
import {
|
|
5
|
+
AuthTemplate,
|
|
6
|
+
ExtractedField,
|
|
7
|
+
ExtractionResult,
|
|
8
|
+
GraphqlArgument,
|
|
9
|
+
GraphqlAuthRequiredError,
|
|
10
|
+
GraphqlExtractionError,
|
|
11
|
+
GraphqlIntegrationConfig,
|
|
12
|
+
GraphqlIntrospectionError,
|
|
13
|
+
GraphqlInvocationError,
|
|
14
|
+
InvocationResult,
|
|
15
|
+
OperationBinding,
|
|
16
|
+
decodeGraphqlIntegrationConfig,
|
|
17
|
+
decodeGraphqlIntegrationConfigOption
|
|
18
|
+
} from "./chunk-N5GJE7R6.js";
|
|
19
|
+
|
|
20
|
+
// src/sdk/introspect.ts
|
|
21
|
+
import { Effect, Option, Schema } from "effect";
|
|
22
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
23
|
+
var INTROSPECTION_QUERY = `
|
|
24
|
+
query IntrospectionQuery {
|
|
25
|
+
__schema {
|
|
26
|
+
queryType { name }
|
|
27
|
+
mutationType { name }
|
|
28
|
+
types {
|
|
29
|
+
kind
|
|
30
|
+
name
|
|
31
|
+
description
|
|
32
|
+
fields(includeDeprecated: false) {
|
|
33
|
+
name
|
|
34
|
+
description
|
|
35
|
+
args {
|
|
36
|
+
name
|
|
37
|
+
description
|
|
38
|
+
type {
|
|
39
|
+
...TypeRef
|
|
40
|
+
}
|
|
41
|
+
defaultValue
|
|
42
|
+
}
|
|
43
|
+
type {
|
|
44
|
+
...TypeRef
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
inputFields {
|
|
48
|
+
name
|
|
49
|
+
description
|
|
50
|
+
type {
|
|
51
|
+
...TypeRef
|
|
52
|
+
}
|
|
53
|
+
defaultValue
|
|
54
|
+
}
|
|
55
|
+
enumValues(includeDeprecated: false) {
|
|
56
|
+
name
|
|
57
|
+
description
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
fragment TypeRef on __Type {
|
|
64
|
+
kind
|
|
65
|
+
name
|
|
66
|
+
ofType {
|
|
67
|
+
kind
|
|
68
|
+
name
|
|
69
|
+
ofType {
|
|
70
|
+
kind
|
|
71
|
+
name
|
|
72
|
+
ofType {
|
|
73
|
+
kind
|
|
74
|
+
name
|
|
75
|
+
ofType {
|
|
76
|
+
kind
|
|
77
|
+
name
|
|
78
|
+
ofType {
|
|
79
|
+
kind
|
|
80
|
+
name
|
|
81
|
+
ofType {
|
|
82
|
+
kind
|
|
83
|
+
name
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
`;
|
|
92
|
+
var IntrospectionTypeRefLeaf = Schema.Struct({
|
|
93
|
+
kind: Schema.String,
|
|
94
|
+
name: Schema.NullOr(Schema.String),
|
|
95
|
+
ofType: Schema.optional(Schema.Null)
|
|
96
|
+
});
|
|
97
|
+
var IntrospectionTypeRef5 = Schema.Struct({
|
|
98
|
+
kind: Schema.String,
|
|
99
|
+
name: Schema.NullOr(Schema.String),
|
|
100
|
+
ofType: Schema.optional(Schema.NullOr(IntrospectionTypeRefLeaf))
|
|
101
|
+
});
|
|
102
|
+
var IntrospectionTypeRef4 = Schema.Struct({
|
|
103
|
+
kind: Schema.String,
|
|
104
|
+
name: Schema.NullOr(Schema.String),
|
|
105
|
+
ofType: Schema.optional(Schema.NullOr(IntrospectionTypeRef5))
|
|
106
|
+
});
|
|
107
|
+
var IntrospectionTypeRef3 = Schema.Struct({
|
|
108
|
+
kind: Schema.String,
|
|
109
|
+
name: Schema.NullOr(Schema.String),
|
|
110
|
+
ofType: Schema.optional(Schema.NullOr(IntrospectionTypeRef4))
|
|
111
|
+
});
|
|
112
|
+
var IntrospectionTypeRef2 = Schema.Struct({
|
|
113
|
+
kind: Schema.String,
|
|
114
|
+
name: Schema.NullOr(Schema.String),
|
|
115
|
+
ofType: Schema.optional(Schema.NullOr(IntrospectionTypeRef3))
|
|
116
|
+
});
|
|
117
|
+
var IntrospectionTypeRefSchema = Schema.Struct({
|
|
118
|
+
kind: Schema.String,
|
|
119
|
+
name: Schema.NullOr(Schema.String),
|
|
120
|
+
ofType: Schema.optional(Schema.NullOr(IntrospectionTypeRef2))
|
|
121
|
+
});
|
|
122
|
+
var IntrospectionInputValueSchema = Schema.Struct({
|
|
123
|
+
name: Schema.String,
|
|
124
|
+
description: Schema.NullOr(Schema.String),
|
|
125
|
+
type: IntrospectionTypeRefSchema,
|
|
126
|
+
defaultValue: Schema.NullOr(Schema.String)
|
|
127
|
+
});
|
|
128
|
+
var IntrospectionFieldSchema = Schema.Struct({
|
|
129
|
+
name: Schema.String,
|
|
130
|
+
description: Schema.NullOr(Schema.String),
|
|
131
|
+
args: Schema.Array(IntrospectionInputValueSchema),
|
|
132
|
+
type: IntrospectionTypeRefSchema
|
|
133
|
+
});
|
|
134
|
+
var IntrospectionTypeSchema = Schema.Struct({
|
|
135
|
+
kind: Schema.String,
|
|
136
|
+
name: Schema.String,
|
|
137
|
+
description: Schema.NullOr(Schema.String),
|
|
138
|
+
fields: Schema.NullOr(Schema.Array(IntrospectionFieldSchema)),
|
|
139
|
+
inputFields: Schema.NullOr(Schema.Array(IntrospectionInputValueSchema)),
|
|
140
|
+
enumValues: Schema.NullOr(
|
|
141
|
+
Schema.Array(
|
|
142
|
+
Schema.Struct({
|
|
143
|
+
name: Schema.String,
|
|
144
|
+
description: Schema.NullOr(Schema.String)
|
|
145
|
+
})
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
});
|
|
149
|
+
var IntrospectionResultSchema = Schema.Struct({
|
|
150
|
+
__schema: Schema.Struct({
|
|
151
|
+
queryType: Schema.NullOr(Schema.Struct({ name: Schema.String })),
|
|
152
|
+
mutationType: Schema.NullOr(Schema.Struct({ name: Schema.String })),
|
|
153
|
+
types: Schema.Array(IntrospectionTypeSchema)
|
|
154
|
+
})
|
|
155
|
+
});
|
|
156
|
+
var IntrospectionResponseSchema = Schema.Struct({
|
|
157
|
+
data: Schema.optional(IntrospectionResultSchema),
|
|
158
|
+
errors: Schema.optional(Schema.Array(Schema.Unknown))
|
|
159
|
+
});
|
|
160
|
+
var UpstreamErrorResponseSchema = Schema.Struct({
|
|
161
|
+
message: Schema.optional(Schema.String),
|
|
162
|
+
errors: Schema.optional(
|
|
163
|
+
Schema.Array(
|
|
164
|
+
Schema.Struct({
|
|
165
|
+
message: Schema.optional(Schema.String)
|
|
166
|
+
})
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
});
|
|
170
|
+
var IntrospectionJsonSchema = Schema.Union([
|
|
171
|
+
Schema.Struct({ data: IntrospectionResultSchema }),
|
|
172
|
+
IntrospectionResultSchema
|
|
173
|
+
]);
|
|
174
|
+
var JsonTextSchema = Schema.fromJsonString(Schema.Unknown);
|
|
175
|
+
var decodeUpstreamErrorResponse = Schema.decodeUnknownOption(UpstreamErrorResponseSchema);
|
|
176
|
+
var firstUpstreamErrorMessage = (value) => {
|
|
177
|
+
const decoded = decodeUpstreamErrorResponse(value);
|
|
178
|
+
return Option.match(decoded, {
|
|
179
|
+
onNone: () => null,
|
|
180
|
+
onSome: (response) => {
|
|
181
|
+
if (response.message) return response.message;
|
|
182
|
+
for (const entry of response.errors ?? []) {
|
|
183
|
+
const message = entry.message;
|
|
184
|
+
if (message) return message;
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
var redactUpstreamBody = (body) => body.replaceAll(
|
|
191
|
+
/("(?:access_token|refresh_token|id_token|client_secret|token|authorization)"\s*:\s*")[^"]*(")/gi,
|
|
192
|
+
"$1[redacted]$2"
|
|
193
|
+
).replaceAll(
|
|
194
|
+
/((?:access_token|refresh_token|id_token|client_secret|token|authorization)=)[^&\s]*/gi,
|
|
195
|
+
"$1[redacted]"
|
|
196
|
+
).replaceAll(
|
|
197
|
+
/((?:authorization|access-token|refresh-token|id-token|client-secret|token)\s*:\s*)(?:bearer\s+)?[^\s,;]+/gi,
|
|
198
|
+
"$1[redacted]"
|
|
199
|
+
);
|
|
200
|
+
var upstreamTextMessage = (body) => {
|
|
201
|
+
const text = redactUpstreamBody(body.replaceAll(/\s+/g, " ").trim());
|
|
202
|
+
if (text.length === 0) return null;
|
|
203
|
+
return text.length > 500 ? `${text.slice(0, 500)}...` : text;
|
|
204
|
+
};
|
|
205
|
+
var introspect = Effect.fn("GraphQL.introspect")(function* (endpoint, headers, queryParams) {
|
|
206
|
+
const client = yield* HttpClient.HttpClient;
|
|
207
|
+
const requestEndpoint = queryParams && Object.keys(queryParams).length > 0 ? (() => {
|
|
208
|
+
const url = new URL(endpoint);
|
|
209
|
+
for (const [name, value] of Object.entries(queryParams)) {
|
|
210
|
+
url.searchParams.set(name, value);
|
|
211
|
+
}
|
|
212
|
+
return url.toString();
|
|
213
|
+
})() : endpoint;
|
|
214
|
+
let request = HttpClientRequest.post(requestEndpoint).pipe(
|
|
215
|
+
HttpClientRequest.setHeader("Content-Type", "application/json"),
|
|
216
|
+
HttpClientRequest.setHeader("Accept", "application/json"),
|
|
217
|
+
HttpClientRequest.setHeader("User-Agent", "executor-graphql"),
|
|
218
|
+
HttpClientRequest.bodyJsonUnsafe({
|
|
219
|
+
query: INTROSPECTION_QUERY
|
|
220
|
+
})
|
|
221
|
+
);
|
|
222
|
+
if (headers) {
|
|
223
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
224
|
+
request = HttpClientRequest.setHeader(request, k, v);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const response = yield* client.execute(request).pipe(
|
|
228
|
+
Effect.tapCause((cause) => Effect.logError("graphql introspection request failed", cause)),
|
|
229
|
+
Effect.mapError(
|
|
230
|
+
() => new GraphqlIntrospectionError({
|
|
231
|
+
message: "Failed to reach GraphQL endpoint"
|
|
232
|
+
})
|
|
233
|
+
)
|
|
234
|
+
);
|
|
235
|
+
if (response.status !== 200) {
|
|
236
|
+
const responseText = yield* response.text.pipe(Effect.catch(() => Effect.succeed("")));
|
|
237
|
+
const raw2 = responseText ? yield* Schema.decodeUnknownEffect(JsonTextSchema)(responseText).pipe(
|
|
238
|
+
Effect.catch(() => Effect.succeed(null))
|
|
239
|
+
) : null;
|
|
240
|
+
const upstreamMessage = upstreamTextMessage(
|
|
241
|
+
(raw2 === null ? null : firstUpstreamErrorMessage(raw2)) ?? responseText
|
|
242
|
+
);
|
|
243
|
+
return yield* new GraphqlIntrospectionError({
|
|
244
|
+
message: upstreamMessage ? `Introspection failed with status ${response.status}: ${upstreamMessage}` : `Introspection failed with status ${response.status}`
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
const raw = yield* response.json.pipe(
|
|
248
|
+
Effect.tapCause((cause) => Effect.logError("graphql introspection JSON parse failed", cause)),
|
|
249
|
+
Effect.mapError(
|
|
250
|
+
() => new GraphqlIntrospectionError({
|
|
251
|
+
message: `Failed to parse introspection response as JSON`
|
|
252
|
+
})
|
|
253
|
+
)
|
|
254
|
+
);
|
|
255
|
+
const json = yield* Schema.decodeUnknownEffect(IntrospectionResponseSchema)(raw).pipe(
|
|
256
|
+
Effect.mapError(
|
|
257
|
+
() => new GraphqlIntrospectionError({
|
|
258
|
+
message: "Introspection response has an invalid shape"
|
|
259
|
+
})
|
|
260
|
+
)
|
|
261
|
+
);
|
|
262
|
+
if (json.errors && Array.isArray(json.errors) && json.errors.length > 0) {
|
|
263
|
+
const upstreamMessage = firstUpstreamErrorMessage(json);
|
|
264
|
+
return yield* new GraphqlIntrospectionError({
|
|
265
|
+
message: upstreamMessage ? `Introspection returned ${json.errors.length} error(s): ${upstreamMessage}` : `Introspection returned ${json.errors.length} error(s)`
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
if (!json.data?.__schema) {
|
|
269
|
+
return yield* new GraphqlIntrospectionError({
|
|
270
|
+
message: "Introspection response missing __schema"
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
return json.data;
|
|
274
|
+
});
|
|
275
|
+
var parseIntrospectionJson = (text) => Schema.decodeUnknownEffect(Schema.fromJsonString(IntrospectionJsonSchema))(text).pipe(
|
|
276
|
+
Effect.map((parsed) => "data" in parsed ? parsed.data : parsed),
|
|
277
|
+
Effect.mapError(
|
|
278
|
+
() => new GraphqlIntrospectionError({
|
|
279
|
+
message: "Failed to parse introspection JSON"
|
|
280
|
+
})
|
|
281
|
+
)
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
// src/sdk/extract.ts
|
|
285
|
+
import { Effect as Effect2, Match, Option as Option2 } from "effect";
|
|
286
|
+
var unwrapTypeName = (ref) => {
|
|
287
|
+
if (ref.name) return ref.name;
|
|
288
|
+
if (ref.ofType) return unwrapTypeName(ref.ofType);
|
|
289
|
+
return "Unknown";
|
|
290
|
+
};
|
|
291
|
+
var isNonNull = (ref) => ref.kind === "NON_NULL";
|
|
292
|
+
var buildDefinitions = (types) => {
|
|
293
|
+
const defs = {};
|
|
294
|
+
for (const [name, type] of types) {
|
|
295
|
+
if (name.startsWith("__")) continue;
|
|
296
|
+
if (type.kind === "INPUT_OBJECT" && type.inputFields) {
|
|
297
|
+
const properties = {};
|
|
298
|
+
const required = [];
|
|
299
|
+
for (const field of type.inputFields) {
|
|
300
|
+
const schema = typeRefToJsonSchema(field.type, types);
|
|
301
|
+
if (field.description) {
|
|
302
|
+
schema.description = field.description;
|
|
303
|
+
}
|
|
304
|
+
properties[field.name] = schema;
|
|
305
|
+
if (isNonNull(field.type)) {
|
|
306
|
+
required.push(field.name);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const def = { type: "object", properties };
|
|
310
|
+
if (required.length > 0) def.required = required;
|
|
311
|
+
if (type.description) def.description = type.description;
|
|
312
|
+
defs[name] = def;
|
|
313
|
+
}
|
|
314
|
+
if (type.kind === "ENUM" && type.enumValues) {
|
|
315
|
+
defs[name] = {
|
|
316
|
+
type: "string",
|
|
317
|
+
enum: type.enumValues.map((v) => v.name),
|
|
318
|
+
...type.description ? { description: type.description } : {}
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return defs;
|
|
323
|
+
};
|
|
324
|
+
var typeRefToJsonSchema = (ref, types) => Match.value(ref.kind).pipe(
|
|
325
|
+
Match.when(
|
|
326
|
+
"NON_NULL",
|
|
327
|
+
() => ref.ofType ? typeRefToJsonSchema(ref.ofType, types) : {}
|
|
328
|
+
),
|
|
329
|
+
Match.when(
|
|
330
|
+
"LIST",
|
|
331
|
+
() => ({
|
|
332
|
+
type: "array",
|
|
333
|
+
items: ref.ofType ? typeRefToJsonSchema(ref.ofType, types) : {}
|
|
334
|
+
})
|
|
335
|
+
),
|
|
336
|
+
Match.when("SCALAR", () => scalarToJsonSchema(ref.name ?? "String")),
|
|
337
|
+
Match.when(
|
|
338
|
+
"ENUM",
|
|
339
|
+
() => ref.name ? { $ref: `#/$defs/${ref.name}` } : { type: "string" }
|
|
340
|
+
),
|
|
341
|
+
Match.when(
|
|
342
|
+
"INPUT_OBJECT",
|
|
343
|
+
() => ref.name ? { $ref: `#/$defs/${ref.name}` } : { type: "object" }
|
|
344
|
+
),
|
|
345
|
+
Match.whenOr(
|
|
346
|
+
"OBJECT",
|
|
347
|
+
"INTERFACE",
|
|
348
|
+
"UNION",
|
|
349
|
+
() => ({ type: "object" })
|
|
350
|
+
),
|
|
351
|
+
Match.option,
|
|
352
|
+
Option2.getOrElse(() => ({}))
|
|
353
|
+
);
|
|
354
|
+
var scalarToJsonSchema = (name) => Match.value(name).pipe(
|
|
355
|
+
Match.whenOr("String", "ID", () => ({ type: "string" })),
|
|
356
|
+
Match.when("Int", () => ({ type: "integer" })),
|
|
357
|
+
Match.when("Float", () => ({ type: "number" })),
|
|
358
|
+
Match.when("Boolean", () => ({ type: "boolean" })),
|
|
359
|
+
Match.option,
|
|
360
|
+
Option2.getOrElse(
|
|
361
|
+
() => ({ type: "string", description: `Custom scalar: ${name}` })
|
|
362
|
+
)
|
|
363
|
+
);
|
|
364
|
+
var buildInputSchema = (args, types) => {
|
|
365
|
+
if (args.length === 0) return void 0;
|
|
366
|
+
const properties = {};
|
|
367
|
+
const required = [];
|
|
368
|
+
for (const arg of args) {
|
|
369
|
+
const schema = typeRefToJsonSchema(arg.type, types);
|
|
370
|
+
if (arg.description) {
|
|
371
|
+
schema.description = arg.description;
|
|
372
|
+
}
|
|
373
|
+
properties[arg.name] = schema;
|
|
374
|
+
if (isNonNull(arg.type)) {
|
|
375
|
+
required.push(arg.name);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const inputSchema = {
|
|
379
|
+
type: "object",
|
|
380
|
+
properties
|
|
381
|
+
};
|
|
382
|
+
if (required.length > 0) inputSchema.required = required;
|
|
383
|
+
return inputSchema;
|
|
384
|
+
};
|
|
385
|
+
var formatTypeRef = (ref) => Match.value(ref.kind).pipe(
|
|
386
|
+
Match.when("NON_NULL", () => ref.ofType ? `${formatTypeRef(ref.ofType)}!` : "Unknown!"),
|
|
387
|
+
Match.when("LIST", () => ref.ofType ? `[${formatTypeRef(ref.ofType)}]` : "[Unknown]"),
|
|
388
|
+
Match.option,
|
|
389
|
+
Option2.getOrElse(() => ref.name ?? "Unknown")
|
|
390
|
+
);
|
|
391
|
+
var extractFields = (_schema, kind, typeName, types) => {
|
|
392
|
+
if (!typeName) return [];
|
|
393
|
+
const type = types.get(typeName);
|
|
394
|
+
if (!type?.fields) return [];
|
|
395
|
+
return type.fields.filter((f) => !f.name.startsWith("__")).map((field) => {
|
|
396
|
+
const args = field.args.map(
|
|
397
|
+
(arg) => GraphqlArgument.make({
|
|
398
|
+
name: arg.name,
|
|
399
|
+
typeName: formatTypeRef(arg.type),
|
|
400
|
+
required: isNonNull(arg.type),
|
|
401
|
+
description: arg.description ? Option2.some(arg.description) : Option2.none()
|
|
402
|
+
})
|
|
403
|
+
);
|
|
404
|
+
const inputSchema = buildInputSchema(field.args, types);
|
|
405
|
+
return ExtractedField.make({
|
|
406
|
+
fieldName: field.name,
|
|
407
|
+
kind,
|
|
408
|
+
description: field.description ? Option2.some(field.description) : Option2.none(),
|
|
409
|
+
arguments: args,
|
|
410
|
+
inputSchema: inputSchema ? Option2.some(inputSchema) : Option2.none(),
|
|
411
|
+
returnTypeName: unwrapTypeName(field.type)
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
};
|
|
415
|
+
var extract = (introspection) => Effect2.try({
|
|
416
|
+
try: () => {
|
|
417
|
+
const schema = introspection.__schema;
|
|
418
|
+
const typeMap = /* @__PURE__ */ new Map();
|
|
419
|
+
for (const t of schema.types) {
|
|
420
|
+
typeMap.set(t.name, t);
|
|
421
|
+
}
|
|
422
|
+
const definitions = buildDefinitions(typeMap);
|
|
423
|
+
const queryFields = extractFields(schema, "query", schema.queryType?.name, typeMap);
|
|
424
|
+
const mutationFields = extractFields(schema, "mutation", schema.mutationType?.name, typeMap);
|
|
425
|
+
return {
|
|
426
|
+
result: ExtractionResult.make({
|
|
427
|
+
schemaName: Option2.none(),
|
|
428
|
+
fields: [...queryFields, ...mutationFields]
|
|
429
|
+
}),
|
|
430
|
+
definitions
|
|
431
|
+
};
|
|
432
|
+
},
|
|
433
|
+
catch: () => new GraphqlExtractionError({
|
|
434
|
+
message: "Failed to extract GraphQL schema"
|
|
435
|
+
})
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// src/sdk/invoke.ts
|
|
439
|
+
import { Effect as Effect3, Option as Option3 } from "effect";
|
|
440
|
+
import { HttpClient as HttpClient2, HttpClientRequest as HttpClientRequest2 } from "effect/unstable/http";
|
|
441
|
+
var endpointWithQueryParams = (endpoint, queryParams) => {
|
|
442
|
+
if (Object.keys(queryParams).length === 0) return endpoint;
|
|
443
|
+
const url = new URL(endpoint);
|
|
444
|
+
for (const [name, value] of Object.entries(queryParams)) {
|
|
445
|
+
url.searchParams.set(name, value);
|
|
446
|
+
}
|
|
447
|
+
return url.toString();
|
|
448
|
+
};
|
|
449
|
+
var endpointForTelemetry = (endpoint) => {
|
|
450
|
+
if (!URL.canParse(endpoint)) return endpoint;
|
|
451
|
+
const url = new URL(endpoint);
|
|
452
|
+
url.search = "";
|
|
453
|
+
url.hash = "";
|
|
454
|
+
return url.toString();
|
|
455
|
+
};
|
|
456
|
+
var isJsonContentType = (ct) => {
|
|
457
|
+
if (!ct) return false;
|
|
458
|
+
const normalized = ct.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
459
|
+
return normalized === "application/json" || normalized.includes("+json") || normalized.includes("json");
|
|
460
|
+
};
|
|
461
|
+
var invoke = Effect3.fn("GraphQL.invoke")(function* (operation, args, endpoint, resolvedHeaders, resolvedQueryParams = {}) {
|
|
462
|
+
const client = yield* HttpClient2.HttpClient;
|
|
463
|
+
const requestEndpoint = endpointWithQueryParams(endpoint, resolvedQueryParams);
|
|
464
|
+
const telemetryEndpoint = endpointForTelemetry(endpoint);
|
|
465
|
+
yield* Effect3.annotateCurrentSpan({
|
|
466
|
+
"http.method": "POST",
|
|
467
|
+
"http.url": telemetryEndpoint,
|
|
468
|
+
"plugin.graphql.endpoint": telemetryEndpoint,
|
|
469
|
+
"plugin.graphql.operation_kind": operation.kind,
|
|
470
|
+
"plugin.graphql.field_name": operation.fieldName,
|
|
471
|
+
"plugin.graphql.headers.resolved_count": Object.keys(resolvedHeaders).length,
|
|
472
|
+
"plugin.graphql.query_params.resolved_count": Object.keys(resolvedQueryParams).length
|
|
473
|
+
});
|
|
474
|
+
const variables = {};
|
|
475
|
+
for (const varName of operation.variableNames) {
|
|
476
|
+
if (args[varName] !== void 0) {
|
|
477
|
+
variables[varName] = args[varName];
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (typeof args.variables === "object" && args.variables !== null) {
|
|
481
|
+
Object.assign(variables, args.variables);
|
|
482
|
+
}
|
|
483
|
+
let request = HttpClientRequest2.post(requestEndpoint).pipe(
|
|
484
|
+
HttpClientRequest2.setHeader("Content-Type", "application/json"),
|
|
485
|
+
HttpClientRequest2.bodyJsonUnsafe({
|
|
486
|
+
query: operation.operationString,
|
|
487
|
+
variables: Object.keys(variables).length > 0 ? variables : void 0
|
|
488
|
+
})
|
|
489
|
+
);
|
|
490
|
+
for (const [name, value] of Object.entries(resolvedHeaders)) {
|
|
491
|
+
request = HttpClientRequest2.setHeader(request, name, value);
|
|
492
|
+
}
|
|
493
|
+
const response = yield* client.execute(request).pipe(
|
|
494
|
+
Effect3.mapError(
|
|
495
|
+
(err) => new GraphqlInvocationError({
|
|
496
|
+
message: "GraphQL request failed",
|
|
497
|
+
statusCode: Option3.none(),
|
|
498
|
+
cause: err
|
|
499
|
+
})
|
|
500
|
+
)
|
|
501
|
+
);
|
|
502
|
+
const status = response.status;
|
|
503
|
+
const contentType = response.headers["content-type"] ?? null;
|
|
504
|
+
const body = isJsonContentType(contentType) ? yield* response.json.pipe(Effect3.catch(() => response.text)) : yield* response.text;
|
|
505
|
+
const gqlBody = body;
|
|
506
|
+
const hasErrors = Array.isArray(gqlBody?.errors) && gqlBody.errors.length > 0;
|
|
507
|
+
yield* Effect3.annotateCurrentSpan({
|
|
508
|
+
"http.status_code": status,
|
|
509
|
+
"plugin.graphql.has_errors": hasErrors,
|
|
510
|
+
"plugin.graphql.error_count": hasErrors ? gqlBody.errors.length : 0
|
|
511
|
+
});
|
|
512
|
+
return InvocationResult.make({
|
|
513
|
+
status,
|
|
514
|
+
data: gqlBody?.data ?? null,
|
|
515
|
+
errors: hasErrors ? gqlBody.errors : null
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
var invokeWithLayer = (operation, args, endpoint, resolvedHeaders, resolvedQueryParams, httpClientLayer) => invoke(operation, args, endpoint, resolvedHeaders, resolvedQueryParams).pipe(
|
|
519
|
+
Effect3.provide(httpClientLayer),
|
|
520
|
+
Effect3.withSpan("plugin.graphql.invoke", {
|
|
521
|
+
attributes: {
|
|
522
|
+
"plugin.graphql.endpoint": endpointForTelemetry(endpoint),
|
|
523
|
+
"plugin.graphql.operation_kind": operation.kind,
|
|
524
|
+
"plugin.graphql.field_name": operation.fieldName
|
|
525
|
+
}
|
|
526
|
+
})
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
// src/sdk/store.ts
|
|
530
|
+
import { Effect as Effect4, Option as Option4, Predicate, Schema as Schema2 } from "effect";
|
|
531
|
+
var CATALOG_OWNER = "org";
|
|
532
|
+
var OPERATION_COLLECTION = "operation";
|
|
533
|
+
var OperationBindingFromJsonString = Schema2.fromJsonString(OperationBinding);
|
|
534
|
+
var decodeOperationBindingFromJsonString = Schema2.decodeUnknownSync(
|
|
535
|
+
OperationBindingFromJsonString
|
|
536
|
+
);
|
|
537
|
+
var decodeOperationBinding = Schema2.decodeUnknownSync(OperationBinding);
|
|
538
|
+
var encodeBinding = Schema2.encodeSync(OperationBinding);
|
|
539
|
+
var decodeBinding = (value) => {
|
|
540
|
+
if (typeof value === "string") return decodeOperationBindingFromJsonString(value);
|
|
541
|
+
return decodeOperationBinding(value);
|
|
542
|
+
};
|
|
543
|
+
var toJsonRecord = (value) => value;
|
|
544
|
+
var OperationStorage = Schema2.Struct({
|
|
545
|
+
toolName: Schema2.String,
|
|
546
|
+
integration: Schema2.String,
|
|
547
|
+
binding: Schema2.Unknown
|
|
548
|
+
});
|
|
549
|
+
var decodeOperationStorage = Schema2.decodeUnknownOption(OperationStorage);
|
|
550
|
+
var operationKey = (integration, toolName) => `${integration}.${toolName}`;
|
|
551
|
+
var operationData = (operation) => ({
|
|
552
|
+
toolName: operation.toolName,
|
|
553
|
+
integration: operation.integration,
|
|
554
|
+
binding: toJsonRecord(encodeBinding(operation.binding))
|
|
555
|
+
});
|
|
556
|
+
var rowToOperation = (row) => {
|
|
557
|
+
const decoded = decodeOperationStorage(row.data);
|
|
558
|
+
if (Option4.isNone(decoded)) return null;
|
|
559
|
+
const operation = decoded.value;
|
|
560
|
+
return {
|
|
561
|
+
toolName: operation.toolName,
|
|
562
|
+
integration: operation.integration,
|
|
563
|
+
binding: decodeBinding(operation.binding)
|
|
564
|
+
};
|
|
565
|
+
};
|
|
566
|
+
var makeDefaultGraphqlStore = ({ pluginStorage }) => {
|
|
567
|
+
const listOperationRows = (integration) => pluginStorage.list({
|
|
568
|
+
collection: OPERATION_COLLECTION,
|
|
569
|
+
keyPrefix: `${integration}.`
|
|
570
|
+
}).pipe(
|
|
571
|
+
Effect4.map(
|
|
572
|
+
(rows) => rows.filter((row) => rowToOperation(row)?.integration === integration)
|
|
573
|
+
)
|
|
574
|
+
);
|
|
575
|
+
const removeOperations = (integration) => Effect4.gen(function* () {
|
|
576
|
+
const rows = yield* listOperationRows(integration);
|
|
577
|
+
for (const row of rows) {
|
|
578
|
+
yield* pluginStorage.remove({
|
|
579
|
+
owner: CATALOG_OWNER,
|
|
580
|
+
collection: OPERATION_COLLECTION,
|
|
581
|
+
key: row.key
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
return {
|
|
586
|
+
replaceOperations: (integration, operations) => Effect4.gen(function* () {
|
|
587
|
+
yield* removeOperations(integration);
|
|
588
|
+
for (const operation of operations) {
|
|
589
|
+
yield* pluginStorage.put({
|
|
590
|
+
owner: CATALOG_OWNER,
|
|
591
|
+
collection: OPERATION_COLLECTION,
|
|
592
|
+
key: operationKey(integration, operation.toolName),
|
|
593
|
+
data: operationData(operation)
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
}),
|
|
597
|
+
getOperation: (integration, toolName) => pluginStorage.get({ collection: OPERATION_COLLECTION, key: operationKey(integration, toolName) }).pipe(Effect4.map((row) => row ? rowToOperation(row) : null)),
|
|
598
|
+
listOperations: (integration) => listOperationRows(integration).pipe(
|
|
599
|
+
Effect4.map((rows) => rows.map(rowToOperation).filter(Predicate.isNotNull))
|
|
600
|
+
),
|
|
601
|
+
removeOperations
|
|
602
|
+
};
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
// src/sdk/plugin.ts
|
|
606
|
+
import { Effect as Effect5, Match as Match2, Option as Option5, Schema as Schema3 } from "effect";
|
|
607
|
+
import { FetchHttpClient } from "effect/unstable/http";
|
|
608
|
+
import {
|
|
609
|
+
authToolFailure,
|
|
610
|
+
definePlugin,
|
|
611
|
+
IntegrationAlreadyExistsError,
|
|
612
|
+
IntegrationDetectionResult,
|
|
613
|
+
IntegrationSlug,
|
|
614
|
+
ToolName,
|
|
615
|
+
ToolResult
|
|
616
|
+
} from "@executor-js/sdk/core";
|
|
617
|
+
var GraphqlErrorBody = Schema3.Struct({ message: Schema3.String });
|
|
618
|
+
var GraphqlErrorsBody = Schema3.Array(Schema3.Unknown);
|
|
619
|
+
var decodeGraphqlErrorBody = Schema3.decodeUnknownOption(GraphqlErrorBody);
|
|
620
|
+
var decodeGraphqlErrorsBody = Schema3.decodeUnknownOption(GraphqlErrorsBody);
|
|
621
|
+
var decodeGraphqlErrors = (errors) => Option5.getOrUndefined(decodeGraphqlErrorsBody(errors));
|
|
622
|
+
var extractGraphqlErrorMessage = (errors) => errors.map((error) => Option5.getOrUndefined(decodeGraphqlErrorBody(error))?.message).find((message) => message !== void 0 && message.length > 0);
|
|
623
|
+
var GRAPHQL_PLUGIN_ID = "graphql";
|
|
624
|
+
var AuthTemplateSchema = AuthTemplate;
|
|
625
|
+
var GraphqlAddIntegrationInputSchema = Schema3.Struct({
|
|
626
|
+
endpoint: Schema3.String,
|
|
627
|
+
slug: Schema3.optional(Schema3.String),
|
|
628
|
+
name: Schema3.optional(Schema3.String),
|
|
629
|
+
introspectionJson: Schema3.optional(Schema3.String),
|
|
630
|
+
headers: Schema3.optional(Schema3.Record(Schema3.String, Schema3.String)),
|
|
631
|
+
queryParams: Schema3.optional(Schema3.Record(Schema3.String, Schema3.String)),
|
|
632
|
+
authenticationTemplate: Schema3.optional(Schema3.Array(AuthTemplateSchema))
|
|
633
|
+
});
|
|
634
|
+
var GraphqlConfigureInputSchema = Schema3.Struct({
|
|
635
|
+
name: Schema3.optional(Schema3.String),
|
|
636
|
+
endpoint: Schema3.optional(Schema3.String),
|
|
637
|
+
headers: Schema3.optional(Schema3.Record(Schema3.String, Schema3.String)),
|
|
638
|
+
queryParams: Schema3.optional(Schema3.Record(Schema3.String, Schema3.String)),
|
|
639
|
+
authenticationTemplate: Schema3.optional(Schema3.Array(AuthTemplateSchema))
|
|
640
|
+
});
|
|
641
|
+
var GraphqlConfigureAuthInputSchema = Schema3.Struct({
|
|
642
|
+
authenticationTemplate: Schema3.Array(AuthTemplateSchema),
|
|
643
|
+
mode: Schema3.optional(Schema3.Literals(["merge", "replace"]))
|
|
644
|
+
});
|
|
645
|
+
var StaticAddIntegrationOutputSchema = Schema3.Struct({
|
|
646
|
+
slug: Schema3.String,
|
|
647
|
+
name: Schema3.String
|
|
648
|
+
});
|
|
649
|
+
var StaticGetIntegrationInputSchema = Schema3.Struct({
|
|
650
|
+
slug: Schema3.String
|
|
651
|
+
});
|
|
652
|
+
var StaticGetIntegrationOutputSchema = Schema3.Struct({
|
|
653
|
+
integration: Schema3.NullOr(Schema3.Unknown)
|
|
654
|
+
});
|
|
655
|
+
var StaticAddIntegrationInputStandardSchema = Schema3.toStandardSchemaV1(
|
|
656
|
+
Schema3.toStandardJSONSchemaV1(GraphqlAddIntegrationInputSchema)
|
|
657
|
+
);
|
|
658
|
+
var StaticAddIntegrationOutputStandardSchema = Schema3.toStandardSchemaV1(
|
|
659
|
+
Schema3.toStandardJSONSchemaV1(StaticAddIntegrationOutputSchema)
|
|
660
|
+
);
|
|
661
|
+
var StaticGetIntegrationInputStandardSchema = Schema3.toStandardSchemaV1(
|
|
662
|
+
Schema3.toStandardJSONSchemaV1(StaticGetIntegrationInputSchema)
|
|
663
|
+
);
|
|
664
|
+
var StaticGetIntegrationOutputStandardSchema = Schema3.toStandardSchemaV1(
|
|
665
|
+
Schema3.toStandardJSONSchemaV1(StaticGetIntegrationOutputSchema)
|
|
666
|
+
);
|
|
667
|
+
var graphqlToolFailure = (code, message, details) => ToolResult.fail({
|
|
668
|
+
code,
|
|
669
|
+
message,
|
|
670
|
+
...details === void 0 ? {} : { details }
|
|
671
|
+
});
|
|
672
|
+
var graphqlAuthToolFailure = (failure) => authToolFailure({
|
|
673
|
+
code: failure.code,
|
|
674
|
+
message: failure.message,
|
|
675
|
+
source: { id: failure.integration, scope: failure.owner },
|
|
676
|
+
credential: {
|
|
677
|
+
kind: failure.credentialKind,
|
|
678
|
+
...failure.credentialLabel ? { label: failure.credentialLabel } : {},
|
|
679
|
+
connectionId: failure.connection
|
|
680
|
+
},
|
|
681
|
+
...failure.status !== void 0 ? { status: failure.status } : {},
|
|
682
|
+
...failure.details !== void 0 ? {
|
|
683
|
+
upstream: {
|
|
684
|
+
...failure.status !== void 0 ? { status: failure.status } : {},
|
|
685
|
+
details: failure.details
|
|
686
|
+
}
|
|
687
|
+
} : {}
|
|
688
|
+
});
|
|
689
|
+
var urlMatchesToken = (url, token) => {
|
|
690
|
+
const re = new RegExp(`(?:^|[^a-z0-9])${token}(?:$|[^a-z0-9])`, "i");
|
|
691
|
+
return re.test(url.hostname) || re.test(url.pathname);
|
|
692
|
+
};
|
|
693
|
+
var slugFromEndpoint = (endpoint) => {
|
|
694
|
+
try {
|
|
695
|
+
const url = new URL(endpoint);
|
|
696
|
+
return url.hostname.replace(/[^a-z0-9]+/gi, "_").toLowerCase();
|
|
697
|
+
} catch {
|
|
698
|
+
return "graphql";
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
var formatTypeRef2 = (ref) => Match2.value(ref.kind).pipe(
|
|
702
|
+
Match2.when("NON_NULL", () => ref.ofType ? `${formatTypeRef2(ref.ofType)}!` : "Unknown!"),
|
|
703
|
+
Match2.when("LIST", () => ref.ofType ? `[${formatTypeRef2(ref.ofType)}]` : "[Unknown]"),
|
|
704
|
+
Match2.option,
|
|
705
|
+
Option5.getOrElse(() => ref.name ?? "Unknown")
|
|
706
|
+
);
|
|
707
|
+
var unwrapTypeName2 = (ref) => {
|
|
708
|
+
if (ref.name) return ref.name;
|
|
709
|
+
if (ref.ofType) return unwrapTypeName2(ref.ofType);
|
|
710
|
+
return "Unknown";
|
|
711
|
+
};
|
|
712
|
+
var buildSelectionSet = (ref, types, depth, seen) => {
|
|
713
|
+
if (depth > 2) return "";
|
|
714
|
+
const leafName = unwrapTypeName2(ref);
|
|
715
|
+
if (seen.has(leafName)) return "";
|
|
716
|
+
const objectType = types.get(leafName);
|
|
717
|
+
if (!objectType?.fields) return "";
|
|
718
|
+
const kind = objectType.kind;
|
|
719
|
+
if (kind === "SCALAR" || kind === "ENUM") return "";
|
|
720
|
+
seen.add(leafName);
|
|
721
|
+
const subFields = objectType.fields.filter((f) => !f.name.startsWith("__")).slice(0, 12).map((f) => {
|
|
722
|
+
const sub = buildSelectionSet(f.type, types, depth + 1, seen);
|
|
723
|
+
return sub ? `${f.name} ${sub}` : f.name;
|
|
724
|
+
});
|
|
725
|
+
seen.delete(leafName);
|
|
726
|
+
return subFields.length > 0 ? `{ ${subFields.join(" ")} }` : "";
|
|
727
|
+
};
|
|
728
|
+
var operationNameForField = (fieldName) => fieldName.charAt(0).toUpperCase() + fieldName.slice(1);
|
|
729
|
+
var buildOperationStringForField = (kind, field, types) => {
|
|
730
|
+
const opType = kind === "query" ? "query" : "mutation";
|
|
731
|
+
const opName = operationNameForField(field.name);
|
|
732
|
+
const varDefs = field.args.map((arg) => {
|
|
733
|
+
const typeName = formatTypeRef2(arg.type);
|
|
734
|
+
return `$${arg.name}: ${typeName}`;
|
|
735
|
+
});
|
|
736
|
+
const argPasses = field.args.map((arg) => `${arg.name}: $${arg.name}`);
|
|
737
|
+
const selectionSet = buildSelectionSet(field.type, types, 0, /* @__PURE__ */ new Set());
|
|
738
|
+
const varDefsStr = varDefs.length > 0 ? `(${varDefs.join(", ")})` : "";
|
|
739
|
+
const argPassStr = argPasses.length > 0 ? `(${argPasses.join(", ")})` : "";
|
|
740
|
+
return `${opType} ${opName}${varDefsStr} { ${field.name}${argPassStr}${selectionSet ? ` ${selectionSet}` : ""} }`;
|
|
741
|
+
};
|
|
742
|
+
var prepareOperations = (fields, introspection) => {
|
|
743
|
+
const typeMap = /* @__PURE__ */ new Map();
|
|
744
|
+
for (const t of introspection.__schema.types) {
|
|
745
|
+
typeMap.set(t.name, t);
|
|
746
|
+
}
|
|
747
|
+
const fieldMap = /* @__PURE__ */ new Map();
|
|
748
|
+
const schema = introspection.__schema;
|
|
749
|
+
for (const rootKind of ["query", "mutation"]) {
|
|
750
|
+
const typeName = rootKind === "query" ? schema.queryType?.name : schema.mutationType?.name;
|
|
751
|
+
if (!typeName) continue;
|
|
752
|
+
const rootType = typeMap.get(typeName);
|
|
753
|
+
if (!rootType?.fields) continue;
|
|
754
|
+
for (const f of rootType.fields) {
|
|
755
|
+
if (!f.name.startsWith("__")) {
|
|
756
|
+
fieldMap.set(`${rootKind}.${f.name}`, { kind: rootKind, field: f });
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return fields.map((extracted) => {
|
|
761
|
+
const prefix = extracted.kind === "mutation" ? "mutation" : "query";
|
|
762
|
+
const toolName = `${prefix}.${extracted.fieldName}`;
|
|
763
|
+
const description = Option5.getOrElse(
|
|
764
|
+
extracted.description,
|
|
765
|
+
() => `GraphQL ${extracted.kind}: ${extracted.fieldName} -> ${extracted.returnTypeName}`
|
|
766
|
+
);
|
|
767
|
+
const key = `${extracted.kind}.${extracted.fieldName}`;
|
|
768
|
+
const entry = fieldMap.get(key);
|
|
769
|
+
const operationString = entry ? buildOperationStringForField(entry.kind, entry.field, typeMap) : `${extracted.kind} ${operationNameForField(extracted.fieldName)} { ${extracted.fieldName} }`;
|
|
770
|
+
const binding = OperationBinding.make({
|
|
771
|
+
kind: extracted.kind,
|
|
772
|
+
fieldName: extracted.fieldName,
|
|
773
|
+
operationString,
|
|
774
|
+
variableNames: extracted.arguments.map((a) => a.name)
|
|
775
|
+
});
|
|
776
|
+
return {
|
|
777
|
+
toolName,
|
|
778
|
+
description,
|
|
779
|
+
inputSchema: Option5.getOrUndefined(extracted.inputSchema),
|
|
780
|
+
binding
|
|
781
|
+
};
|
|
782
|
+
});
|
|
783
|
+
};
|
|
784
|
+
var annotationsFor = (binding) => {
|
|
785
|
+
if (binding.kind === "mutation") {
|
|
786
|
+
return {
|
|
787
|
+
requiresApproval: true,
|
|
788
|
+
approvalDescription: `mutation ${binding.fieldName}`
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
return {};
|
|
792
|
+
};
|
|
793
|
+
var renderAuthTemplate = (template, value) => {
|
|
794
|
+
if (template.kind === "oauth2") {
|
|
795
|
+
const header = template.header ?? "Authorization";
|
|
796
|
+
const prefix = template.prefix ?? "Bearer ";
|
|
797
|
+
return { headers: { [header]: `${prefix}${value}` }, queryParams: {} };
|
|
798
|
+
}
|
|
799
|
+
const rendered = template.prefix ? `${template.prefix}${value}` : value;
|
|
800
|
+
if (template.in === "query") {
|
|
801
|
+
return { headers: {}, queryParams: { [template.name]: rendered } };
|
|
802
|
+
}
|
|
803
|
+
return { headers: { [template.name]: rendered }, queryParams: {} };
|
|
804
|
+
};
|
|
805
|
+
var buildToolDefs = (prepared) => prepared.map((p) => ({
|
|
806
|
+
name: ToolName.make(p.toolName),
|
|
807
|
+
description: p.description,
|
|
808
|
+
inputSchema: p.inputSchema,
|
|
809
|
+
annotations: annotationsFor(p.binding)
|
|
810
|
+
}));
|
|
811
|
+
var toStoredOperations = (slug, prepared) => prepared.map((p) => ({
|
|
812
|
+
toolName: p.toolName,
|
|
813
|
+
integration: String(slug),
|
|
814
|
+
binding: p.binding
|
|
815
|
+
}));
|
|
816
|
+
var introspectHeadersForConnection = (config, credentialValue) => {
|
|
817
|
+
const headers = { ...config.headers ?? {} };
|
|
818
|
+
const queryParams = { ...config.queryParams ?? {} };
|
|
819
|
+
if (credentialValue !== null) {
|
|
820
|
+
const template = config.authenticationTemplate[0];
|
|
821
|
+
if (template) {
|
|
822
|
+
const rendered = renderAuthTemplate(template, credentialValue);
|
|
823
|
+
Object.assign(headers, rendered.headers);
|
|
824
|
+
Object.assign(queryParams, rendered.queryParams);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
return { headers, queryParams };
|
|
828
|
+
};
|
|
829
|
+
var introspectForConnection = (config, credentialValue, httpClientLayer) => {
|
|
830
|
+
if (config.introspectionJson) {
|
|
831
|
+
return parseIntrospectionJson(config.introspectionJson);
|
|
832
|
+
}
|
|
833
|
+
const auth = introspectHeadersForConnection(config, credentialValue);
|
|
834
|
+
return introspect(
|
|
835
|
+
config.endpoint,
|
|
836
|
+
Object.keys(auth.headers).length > 0 ? auth.headers : void 0,
|
|
837
|
+
Object.keys(auth.queryParams).length > 0 ? auth.queryParams : void 0
|
|
838
|
+
).pipe(Effect5.provide(httpClientLayer));
|
|
839
|
+
};
|
|
840
|
+
var materializeOperations = (ctx, integration, config, credential, httpClientLayer) => Effect5.gen(function* () {
|
|
841
|
+
const template = config.authenticationTemplate.find(
|
|
842
|
+
(t) => t.slug === String(credential.template)
|
|
843
|
+
);
|
|
844
|
+
const headers = { ...config.headers ?? {} };
|
|
845
|
+
const queryParams = {
|
|
846
|
+
...config.queryParams ?? {}
|
|
847
|
+
};
|
|
848
|
+
if (template && credential.value !== null) {
|
|
849
|
+
const rendered = renderAuthTemplate(template, credential.value);
|
|
850
|
+
Object.assign(headers, rendered.headers);
|
|
851
|
+
Object.assign(queryParams, rendered.queryParams);
|
|
852
|
+
}
|
|
853
|
+
const introspection = config.introspectionJson ? yield* parseIntrospectionJson(config.introspectionJson) : yield* introspect(
|
|
854
|
+
config.endpoint,
|
|
855
|
+
Object.keys(headers).length > 0 ? headers : void 0,
|
|
856
|
+
Object.keys(queryParams).length > 0 ? queryParams : void 0
|
|
857
|
+
).pipe(Effect5.provide(httpClientLayer));
|
|
858
|
+
const { result } = yield* extract(introspection).pipe(
|
|
859
|
+
Effect5.catch(
|
|
860
|
+
() => Effect5.succeed({
|
|
861
|
+
result: { fields: [] }
|
|
862
|
+
})
|
|
863
|
+
)
|
|
864
|
+
);
|
|
865
|
+
const prepared = prepareOperations(result.fields, introspection);
|
|
866
|
+
const stored = toStoredOperations(IntegrationSlug.make(integration), prepared);
|
|
867
|
+
yield* ctx.storage.replaceOperations(integration, stored);
|
|
868
|
+
return stored;
|
|
869
|
+
});
|
|
870
|
+
var graphqlApiKeyLabel = (placement) => `API key (${placement.name || (placement.carrier === "header" ? "header" : "query")})`;
|
|
871
|
+
var describeGraphqlAuthMethods = (record) => {
|
|
872
|
+
const config = Option5.getOrUndefined(decodeGraphqlIntegrationConfigOption(record.config));
|
|
873
|
+
if (!config) return [];
|
|
874
|
+
return config.authenticationTemplate.map((template) => {
|
|
875
|
+
const slug = template.slug;
|
|
876
|
+
if (template.kind === "oauth2") {
|
|
877
|
+
return {
|
|
878
|
+
id: slug,
|
|
879
|
+
label: "OAuth",
|
|
880
|
+
kind: "oauth",
|
|
881
|
+
template: slug,
|
|
882
|
+
oauth: {}
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
const placement = {
|
|
886
|
+
carrier: template.in,
|
|
887
|
+
name: template.name,
|
|
888
|
+
prefix: template.prefix ?? ""
|
|
889
|
+
};
|
|
890
|
+
return {
|
|
891
|
+
id: slug,
|
|
892
|
+
label: graphqlApiKeyLabel(placement),
|
|
893
|
+
kind: "apikey",
|
|
894
|
+
template: slug,
|
|
895
|
+
placements: [placement]
|
|
896
|
+
};
|
|
897
|
+
});
|
|
898
|
+
};
|
|
899
|
+
var describeGraphqlIntegrationDisplay = (record) => {
|
|
900
|
+
const config = Option5.getOrUndefined(decodeGraphqlIntegrationConfigOption(record.config));
|
|
901
|
+
return { url: config?.endpoint };
|
|
902
|
+
};
|
|
903
|
+
var shortId = () => Math.random().toString(36).slice(2, 8);
|
|
904
|
+
var freshCustomSlug = (taken) => {
|
|
905
|
+
let candidate = `custom_${shortId()}`;
|
|
906
|
+
while (taken.has(candidate)) candidate = `custom_${shortId()}`;
|
|
907
|
+
return candidate;
|
|
908
|
+
};
|
|
909
|
+
var mergeGraphqlAuthTemplate = (existing, incoming) => {
|
|
910
|
+
const result = existing.map((entry) => entry);
|
|
911
|
+
const taken = new Set(result.map((entry) => entry.slug));
|
|
912
|
+
for (const entry of incoming) {
|
|
913
|
+
const requested = entry.slug.trim();
|
|
914
|
+
const existingIndex = result.findIndex((current) => current.slug === requested);
|
|
915
|
+
if (requested.length > 0 && existingIndex >= 0) {
|
|
916
|
+
result[existingIndex] = entry;
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
const slug = requested.length > 0 && !taken.has(requested) ? requested : freshCustomSlug(taken);
|
|
920
|
+
taken.add(slug);
|
|
921
|
+
result.push({ ...entry, slug });
|
|
922
|
+
}
|
|
923
|
+
return result;
|
|
924
|
+
};
|
|
925
|
+
var makeGraphqlExtension = (ctx) => {
|
|
926
|
+
const buildConfig = (input) => GraphqlIntegrationConfig.make({
|
|
927
|
+
endpoint: input.endpoint,
|
|
928
|
+
name: input.name?.trim() || slugFromEndpoint(input.endpoint),
|
|
929
|
+
...input.introspectionJson !== void 0 ? { introspectionJson: input.introspectionJson } : {},
|
|
930
|
+
...input.headers !== void 0 ? { headers: input.headers } : {},
|
|
931
|
+
...input.queryParams !== void 0 ? { queryParams: input.queryParams } : {},
|
|
932
|
+
authenticationTemplate: input.authenticationTemplate ?? []
|
|
933
|
+
});
|
|
934
|
+
const addIntegrationInternal = (input) => Effect5.gen(function* () {
|
|
935
|
+
const slug = IntegrationSlug.make(input.slug ?? slugFromEndpoint(input.endpoint));
|
|
936
|
+
const existing = yield* ctx.core.integrations.get(slug);
|
|
937
|
+
if (existing) {
|
|
938
|
+
return yield* new IntegrationAlreadyExistsError({ slug });
|
|
939
|
+
}
|
|
940
|
+
return yield* addIntegrationTransaction(input, slug);
|
|
941
|
+
});
|
|
942
|
+
const addIntegrationTransaction = (input, slug) => ctx.transaction(
|
|
943
|
+
Effect5.gen(function* () {
|
|
944
|
+
const baseConfig = buildConfig(input);
|
|
945
|
+
if (baseConfig.introspectionJson === void 0) {
|
|
946
|
+
yield* ctx.core.integrations.register({
|
|
947
|
+
slug,
|
|
948
|
+
description: baseConfig.name,
|
|
949
|
+
config: baseConfig,
|
|
950
|
+
canRemove: true,
|
|
951
|
+
canRefresh: true
|
|
952
|
+
});
|
|
953
|
+
return { slug: String(slug), name: baseConfig.name, toolCount: 0 };
|
|
954
|
+
}
|
|
955
|
+
const introspection = yield* parseIntrospectionJson(baseConfig.introspectionJson);
|
|
956
|
+
const { result } = yield* extract(introspection);
|
|
957
|
+
const prepared = prepareOperations(result.fields, introspection);
|
|
958
|
+
const config = GraphqlIntegrationConfig.make({
|
|
959
|
+
...baseConfig,
|
|
960
|
+
introspectionJson: JSON.stringify({ data: introspection })
|
|
961
|
+
});
|
|
962
|
+
yield* ctx.storage.replaceOperations(String(slug), toStoredOperations(slug, prepared));
|
|
963
|
+
yield* ctx.core.integrations.register({
|
|
964
|
+
slug,
|
|
965
|
+
description: config.name,
|
|
966
|
+
config,
|
|
967
|
+
canRemove: true,
|
|
968
|
+
canRefresh: true
|
|
969
|
+
});
|
|
970
|
+
return {
|
|
971
|
+
slug: String(slug),
|
|
972
|
+
name: config.name,
|
|
973
|
+
toolCount: prepared.length
|
|
974
|
+
};
|
|
975
|
+
})
|
|
976
|
+
);
|
|
977
|
+
const configureIntegration = (slug, input) => Effect5.gen(function* () {
|
|
978
|
+
const record = yield* ctx.core.integrations.get(IntegrationSlug.make(slug));
|
|
979
|
+
if (!record) return;
|
|
980
|
+
const current = Option5.getOrElse(
|
|
981
|
+
// best-effort: re-decode the stored config, falling back to an
|
|
982
|
+
// endpoint-only config if it was never set.
|
|
983
|
+
yield* decodeGraphqlIntegrationConfig(record.config).pipe(Effect5.option),
|
|
984
|
+
() => GraphqlIntegrationConfig.make({
|
|
985
|
+
endpoint: "",
|
|
986
|
+
name: record.description,
|
|
987
|
+
authenticationTemplate: []
|
|
988
|
+
})
|
|
989
|
+
);
|
|
990
|
+
const next = GraphqlIntegrationConfig.make({
|
|
991
|
+
endpoint: input.endpoint ?? current.endpoint,
|
|
992
|
+
name: input.name?.trim() || current.name,
|
|
993
|
+
...current.introspectionJson !== void 0 ? { introspectionJson: current.introspectionJson } : {},
|
|
994
|
+
...(input.headers ?? current.headers) !== void 0 ? { headers: input.headers ?? current.headers } : {},
|
|
995
|
+
...(input.queryParams ?? current.queryParams) !== void 0 ? { queryParams: input.queryParams ?? current.queryParams } : {},
|
|
996
|
+
authenticationTemplate: input.authenticationTemplate ?? current.authenticationTemplate
|
|
997
|
+
});
|
|
998
|
+
yield* ctx.core.integrations.update(IntegrationSlug.make(slug), {
|
|
999
|
+
description: next.name,
|
|
1000
|
+
config: next
|
|
1001
|
+
});
|
|
1002
|
+
});
|
|
1003
|
+
const getConfig = (slug) => ctx.core.integrations.get(IntegrationSlug.make(slug)).pipe(
|
|
1004
|
+
Effect5.map(
|
|
1005
|
+
(record) => record ? Option5.getOrNull(decodeGraphqlIntegrationConfigOption(record.config)) : null
|
|
1006
|
+
)
|
|
1007
|
+
);
|
|
1008
|
+
const configureAuthMethods = (slug, input) => ctx.transaction(
|
|
1009
|
+
Effect5.gen(function* () {
|
|
1010
|
+
const record = yield* ctx.core.integrations.get(IntegrationSlug.make(slug));
|
|
1011
|
+
if (!record) return [];
|
|
1012
|
+
const current = Option5.getOrNull(decodeGraphqlIntegrationConfigOption(record.config));
|
|
1013
|
+
if (!current) return [];
|
|
1014
|
+
const merged = input.mode === "replace" ? input.authenticationTemplate : mergeGraphqlAuthTemplate(
|
|
1015
|
+
current.authenticationTemplate,
|
|
1016
|
+
input.authenticationTemplate
|
|
1017
|
+
);
|
|
1018
|
+
const next = GraphqlIntegrationConfig.make({
|
|
1019
|
+
...current,
|
|
1020
|
+
authenticationTemplate: merged
|
|
1021
|
+
});
|
|
1022
|
+
yield* ctx.core.integrations.update(IntegrationSlug.make(slug), {
|
|
1023
|
+
config: next
|
|
1024
|
+
});
|
|
1025
|
+
return merged;
|
|
1026
|
+
})
|
|
1027
|
+
);
|
|
1028
|
+
return {
|
|
1029
|
+
/** Register a GraphQL integration (introspects + persists operations). */
|
|
1030
|
+
addIntegration: (input) => addIntegrationInternal(input),
|
|
1031
|
+
/** Read the integration's stored config. */
|
|
1032
|
+
getIntegration: (slug) => ctx.core.integrations.get(IntegrationSlug.make(slug)).pipe(Effect5.map((record) => record ? record.config : null)),
|
|
1033
|
+
/** Read the integration's decoded config (auth templates surfaced). */
|
|
1034
|
+
getConfig,
|
|
1035
|
+
/** Merge-append custom auth methods (custom-method-create flow). */
|
|
1036
|
+
configureAuth: configureAuthMethods,
|
|
1037
|
+
removeIntegration: (slug) => ctx.transaction(
|
|
1038
|
+
Effect5.gen(function* () {
|
|
1039
|
+
yield* ctx.storage.removeOperations(slug);
|
|
1040
|
+
yield* ctx.core.integrations.remove(IntegrationSlug.make(slug)).pipe(Effect5.catchTag("IntegrationRemovalNotAllowedError", () => Effect5.void));
|
|
1041
|
+
})
|
|
1042
|
+
),
|
|
1043
|
+
configure: configureIntegration
|
|
1044
|
+
};
|
|
1045
|
+
};
|
|
1046
|
+
var graphqlPlugin = definePlugin((options) => {
|
|
1047
|
+
return {
|
|
1048
|
+
id: GRAPHQL_PLUGIN_ID,
|
|
1049
|
+
packageName: "@executor-js/plugin-graphql",
|
|
1050
|
+
integrationPresets: graphqlPresets.map((preset) => ({
|
|
1051
|
+
id: preset.id,
|
|
1052
|
+
name: preset.name,
|
|
1053
|
+
summary: preset.summary,
|
|
1054
|
+
url: preset.url,
|
|
1055
|
+
endpoint: preset.endpoint,
|
|
1056
|
+
...preset.icon ? { icon: preset.icon } : {},
|
|
1057
|
+
...preset.featured ? { featured: preset.featured } : {}
|
|
1058
|
+
})),
|
|
1059
|
+
storage: (deps) => makeDefaultGraphqlStore(deps),
|
|
1060
|
+
extension: (ctx) => makeGraphqlExtension(ctx),
|
|
1061
|
+
integrationConfigure: {
|
|
1062
|
+
type: "graphql",
|
|
1063
|
+
schema: GraphqlConfigureInputSchema,
|
|
1064
|
+
configure: ({ ctx, integration, config }) => makeGraphqlExtension(ctx).configure(String(integration), config)
|
|
1065
|
+
},
|
|
1066
|
+
describeAuthMethods: describeGraphqlAuthMethods,
|
|
1067
|
+
describeIntegrationDisplay: describeGraphqlIntegrationDisplay,
|
|
1068
|
+
staticSources: (self) => [
|
|
1069
|
+
{
|
|
1070
|
+
id: "graphql",
|
|
1071
|
+
kind: "executor",
|
|
1072
|
+
name: "GraphQL",
|
|
1073
|
+
tools: [
|
|
1074
|
+
{
|
|
1075
|
+
name: "getIntegration",
|
|
1076
|
+
description: "Inspect an existing GraphQL integration, including endpoint, static headers/query params, and auth templates. Use this before repairing an integration with `graphql.configure` or creating a connection.",
|
|
1077
|
+
inputSchema: StaticGetIntegrationInputStandardSchema,
|
|
1078
|
+
outputSchema: StaticGetIntegrationOutputStandardSchema,
|
|
1079
|
+
handler: ({ args }) => {
|
|
1080
|
+
const input = args;
|
|
1081
|
+
return Effect5.map(
|
|
1082
|
+
self.getIntegration(input.slug),
|
|
1083
|
+
(integration) => ToolResult.ok({ integration })
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
},
|
|
1087
|
+
{
|
|
1088
|
+
name: "addIntegration",
|
|
1089
|
+
description: "Add a GraphQL endpoint to the catalog and register its operations. Introspects the endpoint (or uses provided introspection JSON). After adding, create an owner-scoped connection against the integration to materialize its per-connection tools. For API keys / bearer tokens, declare an `authenticationTemplate` and create a connection whose value is the token.",
|
|
1090
|
+
annotations: {
|
|
1091
|
+
requiresApproval: true,
|
|
1092
|
+
approvalDescription: "Add a GraphQL integration"
|
|
1093
|
+
},
|
|
1094
|
+
inputSchema: StaticAddIntegrationInputStandardSchema,
|
|
1095
|
+
outputSchema: StaticAddIntegrationOutputStandardSchema,
|
|
1096
|
+
handler: ({ args }) => {
|
|
1097
|
+
const input = args;
|
|
1098
|
+
return self.addIntegration(input).pipe(
|
|
1099
|
+
Effect5.map((result) => ToolResult.ok({ slug: result.slug, name: result.name })),
|
|
1100
|
+
Effect5.catchTags({
|
|
1101
|
+
GraphqlIntrospectionError: ({ message }) => Effect5.succeed(graphqlToolFailure("graphql_introspection_failed", message)),
|
|
1102
|
+
GraphqlExtractionError: ({ message }) => Effect5.succeed(graphqlToolFailure("graphql_extraction_failed", message)),
|
|
1103
|
+
IntegrationAlreadyExistsError: ({ slug }) => Effect5.succeed(
|
|
1104
|
+
graphqlToolFailure(
|
|
1105
|
+
"integration_already_exists",
|
|
1106
|
+
`Integration ${slug} already exists; update it instead of re-adding.`
|
|
1107
|
+
)
|
|
1108
|
+
)
|
|
1109
|
+
})
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
]
|
|
1114
|
+
}
|
|
1115
|
+
],
|
|
1116
|
+
// -----------------------------------------------------------------------
|
|
1117
|
+
// Per-connection tool production. THIS is where a GraphQL integration is
|
|
1118
|
+
// introspected — when a connection is created (or refreshed), with that
|
|
1119
|
+
// connection's credential — yielding one ToolDef per operation. Registering
|
|
1120
|
+
// the integration in the catalog makes no network call; discovery is
|
|
1121
|
+
// deferred to here, exactly how MCP defers tool discovery to connect time.
|
|
1122
|
+
// The introspected schema is identical across connections, so `invokeTool`
|
|
1123
|
+
// re-derives the same operation bindings; only the credential differs.
|
|
1124
|
+
// -----------------------------------------------------------------------
|
|
1125
|
+
resolveTools: ({
|
|
1126
|
+
config,
|
|
1127
|
+
getValue
|
|
1128
|
+
}) => Effect5.gen(function* () {
|
|
1129
|
+
const decoded = yield* decodeGraphqlIntegrationConfig(config).pipe(Effect5.option);
|
|
1130
|
+
if (Option5.isNone(decoded)) return { tools: [] };
|
|
1131
|
+
const graphqlConfig = decoded.value;
|
|
1132
|
+
const credentialValue = graphqlConfig.introspectionJson === void 0 ? yield* getValue().pipe(Effect5.catch(() => Effect5.succeed(null))) : null;
|
|
1133
|
+
const introspection = yield* introspectForConnection(
|
|
1134
|
+
graphqlConfig,
|
|
1135
|
+
credentialValue,
|
|
1136
|
+
options?.httpClientLayer ?? httpClientLayerFallback
|
|
1137
|
+
).pipe(Effect5.option);
|
|
1138
|
+
if (Option5.isNone(introspection)) return { tools: [] };
|
|
1139
|
+
const extracted = yield* extract(introspection.value).pipe(Effect5.option);
|
|
1140
|
+
if (Option5.isNone(extracted)) return { tools: [] };
|
|
1141
|
+
const prepared = prepareOperations(extracted.value.result.fields, introspection.value);
|
|
1142
|
+
return {
|
|
1143
|
+
tools: buildToolDefs(prepared),
|
|
1144
|
+
definitions: extracted.value.definitions
|
|
1145
|
+
};
|
|
1146
|
+
}).pipe(Effect5.catch(() => Effect5.succeed({ tools: [] }))),
|
|
1147
|
+
// -----------------------------------------------------------------------
|
|
1148
|
+
// Invoke one of a connection's tools. Look up the operation by integration
|
|
1149
|
+
// + tool name, render the credential through the connection's auth
|
|
1150
|
+
// template, and execute the GraphQL request.
|
|
1151
|
+
// -----------------------------------------------------------------------
|
|
1152
|
+
invokeTool: ({ ctx, toolRow, credential, args }) => Effect5.gen(function* () {
|
|
1153
|
+
const httpClientLayer = options?.httpClientLayer ?? ctx.httpClientLayer;
|
|
1154
|
+
const integration = toolRow.integration;
|
|
1155
|
+
const toolName = toolRow.name;
|
|
1156
|
+
const config = yield* decodeGraphqlIntegrationConfig(credential.config).pipe(
|
|
1157
|
+
Effect5.mapError(
|
|
1158
|
+
() => new GraphqlInvocationError({
|
|
1159
|
+
message: `Invalid GraphQL integration config for "${integration}"`,
|
|
1160
|
+
statusCode: Option5.none()
|
|
1161
|
+
})
|
|
1162
|
+
)
|
|
1163
|
+
);
|
|
1164
|
+
let op = yield* ctx.storage.getOperation(integration, toolName);
|
|
1165
|
+
if (!op) {
|
|
1166
|
+
op = yield* materializeOperations(
|
|
1167
|
+
ctx,
|
|
1168
|
+
integration,
|
|
1169
|
+
config,
|
|
1170
|
+
credential,
|
|
1171
|
+
httpClientLayer
|
|
1172
|
+
).pipe(Effect5.map((ops) => ops.find((o) => o.toolName === toolName) ?? null));
|
|
1173
|
+
}
|
|
1174
|
+
if (!op) {
|
|
1175
|
+
return yield* new GraphqlInvocationError({
|
|
1176
|
+
message: `No GraphQL operation found for tool "${integration}.${toolName}"`,
|
|
1177
|
+
statusCode: Option5.none()
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
const headers = { ...config.headers ?? {} };
|
|
1181
|
+
const queryParams = {
|
|
1182
|
+
...config.queryParams ?? {}
|
|
1183
|
+
};
|
|
1184
|
+
const template = config.authenticationTemplate.find(
|
|
1185
|
+
(t) => t.slug === String(credential.template)
|
|
1186
|
+
);
|
|
1187
|
+
if (template) {
|
|
1188
|
+
if (credential.value === null) {
|
|
1189
|
+
return yield* new GraphqlAuthRequiredError({
|
|
1190
|
+
code: template.kind === "oauth2" ? "oauth_connection_missing" : "connection_value_missing",
|
|
1191
|
+
message: template.kind === "oauth2" ? `Missing OAuth connection value for GraphQL integration "${integration}" (connection "${credential.connection}")` : `Missing credential value for GraphQL integration "${integration}" (connection "${credential.connection}")`,
|
|
1192
|
+
owner: credential.owner,
|
|
1193
|
+
integration,
|
|
1194
|
+
connection: String(credential.connection),
|
|
1195
|
+
credentialKind: template.kind === "oauth2" ? "oauth" : "secret",
|
|
1196
|
+
credentialLabel: template.kind === "oauth2" ? "OAuth sign-in" : "API key",
|
|
1197
|
+
template: String(credential.template)
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
const rendered = renderAuthTemplate(template, credential.value);
|
|
1201
|
+
Object.assign(headers, rendered.headers);
|
|
1202
|
+
Object.assign(queryParams, rendered.queryParams);
|
|
1203
|
+
}
|
|
1204
|
+
const result = yield* invokeWithLayer(
|
|
1205
|
+
op.binding,
|
|
1206
|
+
args ?? {},
|
|
1207
|
+
config.endpoint,
|
|
1208
|
+
headers,
|
|
1209
|
+
queryParams,
|
|
1210
|
+
httpClientLayer
|
|
1211
|
+
);
|
|
1212
|
+
const errors = decodeGraphqlErrors(result.errors);
|
|
1213
|
+
if (errors !== void 0 && errors.length > 0) {
|
|
1214
|
+
const firstMessage = extractGraphqlErrorMessage(errors);
|
|
1215
|
+
return ToolResult.fail({
|
|
1216
|
+
code: "graphql_errors",
|
|
1217
|
+
message: firstMessage !== void 0 ? firstMessage : "GraphQL request returned errors",
|
|
1218
|
+
details: { errors }
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
if (result.status < 200 || result.status >= 300) {
|
|
1222
|
+
if (result.status === 401 || result.status === 403) {
|
|
1223
|
+
return authToolFailure({
|
|
1224
|
+
code: "connection_rejected",
|
|
1225
|
+
status: result.status,
|
|
1226
|
+
message: `Upstream rejected credentials for GraphQL integration "${integration}" with HTTP ${result.status}. Re-authenticate or update the connection before retrying this tool.`,
|
|
1227
|
+
source: { id: integration, scope: credential.owner },
|
|
1228
|
+
credential: { kind: "upstream", label: "Upstream authorization" },
|
|
1229
|
+
upstream: {
|
|
1230
|
+
status: result.status,
|
|
1231
|
+
details: {
|
|
1232
|
+
data: result.data,
|
|
1233
|
+
errors: result.errors
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
return ToolResult.fail({
|
|
1239
|
+
code: "graphql_http_error",
|
|
1240
|
+
status: result.status,
|
|
1241
|
+
message: `GraphQL request failed with HTTP ${result.status}`,
|
|
1242
|
+
details: {
|
|
1243
|
+
status: result.status,
|
|
1244
|
+
data: result.data,
|
|
1245
|
+
errors: result.errors
|
|
1246
|
+
}
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
return ToolResult.ok(result.data);
|
|
1250
|
+
}).pipe(
|
|
1251
|
+
Effect5.catchTag(
|
|
1252
|
+
"GraphqlAuthRequiredError",
|
|
1253
|
+
(error) => Effect5.succeed(graphqlAuthToolFailure(error))
|
|
1254
|
+
)
|
|
1255
|
+
),
|
|
1256
|
+
// Per-connection cleanup. Operation bindings are catalog-level (shared
|
|
1257
|
+
// across an integration's connections), so removing a single connection
|
|
1258
|
+
// leaves them in place; the executor drops the connection's tool rows.
|
|
1259
|
+
removeConnection: () => Effect5.void,
|
|
1260
|
+
detect: ({ ctx, url }) => Effect5.gen(function* () {
|
|
1261
|
+
const httpClientLayer = options?.httpClientLayer ?? ctx.httpClientLayer;
|
|
1262
|
+
const trimmed = url.trim();
|
|
1263
|
+
if (!trimmed) return null;
|
|
1264
|
+
const parsed = yield* Effect5.try({
|
|
1265
|
+
try: () => new URL(trimmed),
|
|
1266
|
+
catch: (cause) => cause
|
|
1267
|
+
}).pipe(Effect5.option);
|
|
1268
|
+
if (Option5.isNone(parsed)) return null;
|
|
1269
|
+
const ok = yield* introspect(trimmed).pipe(
|
|
1270
|
+
Effect5.provide(httpClientLayer),
|
|
1271
|
+
Effect5.map(() => true),
|
|
1272
|
+
Effect5.catch(() => Effect5.succeed(false))
|
|
1273
|
+
);
|
|
1274
|
+
const slug = slugFromEndpoint(trimmed);
|
|
1275
|
+
if (ok) {
|
|
1276
|
+
return IntegrationDetectionResult.make({
|
|
1277
|
+
kind: "graphql",
|
|
1278
|
+
confidence: "high",
|
|
1279
|
+
endpoint: trimmed,
|
|
1280
|
+
name: slug,
|
|
1281
|
+
slug
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
if (urlMatchesToken(parsed.value, "graphql")) {
|
|
1285
|
+
return IntegrationDetectionResult.make({
|
|
1286
|
+
kind: "graphql",
|
|
1287
|
+
confidence: "low",
|
|
1288
|
+
endpoint: trimmed,
|
|
1289
|
+
name: slug,
|
|
1290
|
+
slug
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
return null;
|
|
1294
|
+
})
|
|
1295
|
+
};
|
|
1296
|
+
});
|
|
1297
|
+
var httpClientLayerFallback = FetchHttpClient.layer;
|
|
1298
|
+
|
|
1299
|
+
export {
|
|
1300
|
+
introspect,
|
|
1301
|
+
parseIntrospectionJson,
|
|
1302
|
+
extract,
|
|
1303
|
+
invoke,
|
|
1304
|
+
invokeWithLayer,
|
|
1305
|
+
makeDefaultGraphqlStore,
|
|
1306
|
+
describeGraphqlAuthMethods,
|
|
1307
|
+
graphqlPlugin
|
|
1308
|
+
};
|
|
1309
|
+
//# sourceMappingURL=chunk-ADQTI2OK.js.map
|