@executor-js/plugin-graphql 0.0.1 → 0.1.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/README.md +25 -14
- package/dist/api/group.d.ts +138 -33
- package/dist/api/handlers.d.ts +2 -2
- package/dist/chunk-EIC5WI6C.js +1225 -0
- package/dist/chunk-EIC5WI6C.js.map +1 -0
- package/dist/core.js +12 -50
- package/dist/core.js.map +1 -1
- package/dist/index.js +2 -5
- package/dist/index.js.map +1 -1
- package/dist/react/GraphqlSignInButton.d.ts +3 -0
- package/dist/react/atoms.d.ts +78 -0
- package/dist/react/client.d.ts +116 -3
- package/dist/react/defaults.d.ts +2 -0
- package/dist/react/plugin-client.d.ts +2 -0
- package/dist/react/source-plugin.d.ts +1 -1
- package/dist/sdk/errors.d.ts +6 -10
- package/dist/sdk/index.d.ts +4 -6
- package/dist/sdk/introspect.d.ts +2 -2
- package/dist/sdk/invoke.d.ts +7 -13
- package/dist/sdk/plugin.d.ts +231 -14
- package/dist/sdk/store.d.ts +94 -0
- package/dist/sdk/types.d.ts +57 -146
- package/package.json +10 -21
- package/dist/chunk-AC5VPNLE.js +0 -916
- package/dist/chunk-AC5VPNLE.js.map +0 -1
- package/dist/promise.d.ts +0 -7
- package/dist/sdk/config-file-store.d.ts +0 -10
- package/dist/sdk/extract.test.d.ts +0 -1
- package/dist/sdk/kv-operation-store.d.ts +0 -4
- package/dist/sdk/operation-store.d.ts +0 -31
- package/dist/sdk/plugin.test.d.ts +0 -1
- package/dist/sdk/stored-source.d.ts +0 -51
|
@@ -0,0 +1,1225 @@
|
|
|
1
|
+
// src/sdk/errors.ts
|
|
2
|
+
import { Data, Schema } from "effect";
|
|
3
|
+
var GraphqlIntrospectionError = class extends Schema.TaggedErrorClass()(
|
|
4
|
+
"GraphqlIntrospectionError",
|
|
5
|
+
{
|
|
6
|
+
message: Schema.String
|
|
7
|
+
}
|
|
8
|
+
) {
|
|
9
|
+
};
|
|
10
|
+
var GraphqlExtractionError = class extends Schema.TaggedErrorClass()(
|
|
11
|
+
"GraphqlExtractionError",
|
|
12
|
+
{
|
|
13
|
+
message: Schema.String
|
|
14
|
+
}
|
|
15
|
+
) {
|
|
16
|
+
};
|
|
17
|
+
var GraphqlInvocationError = class extends Data.TaggedError("GraphqlInvocationError") {
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// src/sdk/types.ts
|
|
21
|
+
import { Effect, Schema as Schema2 } from "effect";
|
|
22
|
+
import { SecretBackedValue } from "@executor-js/sdk/core";
|
|
23
|
+
var GraphqlOperationKind = Schema2.Literals(["query", "mutation"]);
|
|
24
|
+
var GraphqlArgument = class extends Schema2.Class("GraphqlArgument")({
|
|
25
|
+
name: Schema2.String,
|
|
26
|
+
typeName: Schema2.String,
|
|
27
|
+
required: Schema2.Boolean,
|
|
28
|
+
description: Schema2.OptionFromOptional(Schema2.String)
|
|
29
|
+
}) {
|
|
30
|
+
};
|
|
31
|
+
var ExtractedField = class extends Schema2.Class("ExtractedField")({
|
|
32
|
+
/** e.g. "user", "createUser" */
|
|
33
|
+
fieldName: Schema2.String,
|
|
34
|
+
/** "query" or "mutation" */
|
|
35
|
+
kind: GraphqlOperationKind,
|
|
36
|
+
description: Schema2.OptionFromOptional(Schema2.String),
|
|
37
|
+
arguments: Schema2.Array(GraphqlArgument),
|
|
38
|
+
/** JSON Schema for the input (built from arguments) */
|
|
39
|
+
inputSchema: Schema2.OptionFromOptional(Schema2.Unknown),
|
|
40
|
+
/** The return type name for documentation */
|
|
41
|
+
returnTypeName: Schema2.String
|
|
42
|
+
}) {
|
|
43
|
+
};
|
|
44
|
+
var ExtractionResult = class extends Schema2.Class("ExtractionResult")({
|
|
45
|
+
/** Schema name from introspection */
|
|
46
|
+
schemaName: Schema2.OptionFromOptional(Schema2.String),
|
|
47
|
+
fields: Schema2.Array(ExtractedField)
|
|
48
|
+
}) {
|
|
49
|
+
};
|
|
50
|
+
var OperationBinding = class extends Schema2.Class("OperationBinding")({
|
|
51
|
+
kind: GraphqlOperationKind,
|
|
52
|
+
fieldName: Schema2.String,
|
|
53
|
+
/** The full GraphQL query/mutation string */
|
|
54
|
+
operationString: Schema2.String,
|
|
55
|
+
/** Ordered variable names for mapping */
|
|
56
|
+
variableNames: Schema2.Array(Schema2.String)
|
|
57
|
+
}) {
|
|
58
|
+
};
|
|
59
|
+
var HeaderValue = SecretBackedValue;
|
|
60
|
+
var QueryParamValue = HeaderValue;
|
|
61
|
+
var GraphqlSourceAuth = Schema2.Union(
|
|
62
|
+
[
|
|
63
|
+
Schema2.Struct({ kind: Schema2.Literal("none") }),
|
|
64
|
+
Schema2.Struct({
|
|
65
|
+
kind: Schema2.Literal("oauth2"),
|
|
66
|
+
connectionId: Schema2.String
|
|
67
|
+
})
|
|
68
|
+
]
|
|
69
|
+
);
|
|
70
|
+
var InvocationConfig = class extends Schema2.Class("InvocationConfig")({
|
|
71
|
+
/** The GraphQL endpoint URL */
|
|
72
|
+
endpoint: Schema2.String,
|
|
73
|
+
/** Headers applied to every request. Values can reference secrets. */
|
|
74
|
+
headers: Schema2.Record(Schema2.String, HeaderValue).pipe(
|
|
75
|
+
Schema2.withDecodingDefault(Effect.succeed({})),
|
|
76
|
+
Schema2.withConstructorDefault(Effect.succeed({}))
|
|
77
|
+
),
|
|
78
|
+
/** Query parameters applied to every request. Values can reference secrets. */
|
|
79
|
+
queryParams: Schema2.Record(Schema2.String, QueryParamValue).pipe(
|
|
80
|
+
Schema2.withDecodingDefault(Effect.succeed({})),
|
|
81
|
+
Schema2.withConstructorDefault(Effect.succeed({}))
|
|
82
|
+
)
|
|
83
|
+
}) {
|
|
84
|
+
};
|
|
85
|
+
var InvocationResult = class extends Schema2.Class("InvocationResult")({
|
|
86
|
+
status: Schema2.Number,
|
|
87
|
+
data: Schema2.NullOr(Schema2.Unknown),
|
|
88
|
+
errors: Schema2.NullOr(Schema2.Unknown)
|
|
89
|
+
}) {
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// src/sdk/introspect.ts
|
|
93
|
+
import { Effect as Effect2 } from "effect";
|
|
94
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
95
|
+
var INTROSPECTION_QUERY = `
|
|
96
|
+
query IntrospectionQuery {
|
|
97
|
+
__schema {
|
|
98
|
+
queryType { name }
|
|
99
|
+
mutationType { name }
|
|
100
|
+
types {
|
|
101
|
+
kind
|
|
102
|
+
name
|
|
103
|
+
description
|
|
104
|
+
fields(includeDeprecated: false) {
|
|
105
|
+
name
|
|
106
|
+
description
|
|
107
|
+
args {
|
|
108
|
+
name
|
|
109
|
+
description
|
|
110
|
+
type {
|
|
111
|
+
...TypeRef
|
|
112
|
+
}
|
|
113
|
+
defaultValue
|
|
114
|
+
}
|
|
115
|
+
type {
|
|
116
|
+
...TypeRef
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
inputFields {
|
|
120
|
+
name
|
|
121
|
+
description
|
|
122
|
+
type {
|
|
123
|
+
...TypeRef
|
|
124
|
+
}
|
|
125
|
+
defaultValue
|
|
126
|
+
}
|
|
127
|
+
enumValues(includeDeprecated: false) {
|
|
128
|
+
name
|
|
129
|
+
description
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
fragment TypeRef on __Type {
|
|
136
|
+
kind
|
|
137
|
+
name
|
|
138
|
+
ofType {
|
|
139
|
+
kind
|
|
140
|
+
name
|
|
141
|
+
ofType {
|
|
142
|
+
kind
|
|
143
|
+
name
|
|
144
|
+
ofType {
|
|
145
|
+
kind
|
|
146
|
+
name
|
|
147
|
+
ofType {
|
|
148
|
+
kind
|
|
149
|
+
name
|
|
150
|
+
ofType {
|
|
151
|
+
kind
|
|
152
|
+
name
|
|
153
|
+
ofType {
|
|
154
|
+
kind
|
|
155
|
+
name
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
`;
|
|
164
|
+
var introspect = Effect2.fn("GraphQL.introspect")(function* (endpoint, headers, queryParams) {
|
|
165
|
+
const client = yield* HttpClient.HttpClient;
|
|
166
|
+
const requestEndpoint = queryParams && Object.keys(queryParams).length > 0 ? (() => {
|
|
167
|
+
const url = new URL(endpoint);
|
|
168
|
+
for (const [name, value] of Object.entries(queryParams)) {
|
|
169
|
+
url.searchParams.set(name, value);
|
|
170
|
+
}
|
|
171
|
+
return url.toString();
|
|
172
|
+
})() : endpoint;
|
|
173
|
+
let request = HttpClientRequest.post(requestEndpoint).pipe(
|
|
174
|
+
HttpClientRequest.setHeader("Content-Type", "application/json"),
|
|
175
|
+
HttpClientRequest.bodyJsonUnsafe({
|
|
176
|
+
query: INTROSPECTION_QUERY
|
|
177
|
+
})
|
|
178
|
+
);
|
|
179
|
+
if (headers) {
|
|
180
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
181
|
+
request = HttpClientRequest.setHeader(request, k, v);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const response = yield* client.execute(request).pipe(
|
|
185
|
+
Effect2.tapCause((cause) => Effect2.logError("graphql introspection request failed", cause)),
|
|
186
|
+
Effect2.mapError(
|
|
187
|
+
(err) => new GraphqlIntrospectionError({
|
|
188
|
+
message: `Failed to reach GraphQL endpoint: ${err.message}`
|
|
189
|
+
})
|
|
190
|
+
)
|
|
191
|
+
);
|
|
192
|
+
if (response.status !== 200) {
|
|
193
|
+
return yield* new GraphqlIntrospectionError({
|
|
194
|
+
message: `Introspection failed with status ${response.status}`
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
const raw = yield* response.json.pipe(
|
|
198
|
+
Effect2.tapCause(
|
|
199
|
+
(cause) => Effect2.logError("graphql introspection JSON parse failed", cause)
|
|
200
|
+
),
|
|
201
|
+
Effect2.mapError(
|
|
202
|
+
() => new GraphqlIntrospectionError({
|
|
203
|
+
message: `Failed to parse introspection response as JSON`
|
|
204
|
+
})
|
|
205
|
+
)
|
|
206
|
+
);
|
|
207
|
+
const json = raw;
|
|
208
|
+
if (json.errors && Array.isArray(json.errors) && json.errors.length > 0) {
|
|
209
|
+
return yield* new GraphqlIntrospectionError({
|
|
210
|
+
message: `Introspection returned ${json.errors.length} error(s)`
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
if (!json.data?.__schema) {
|
|
214
|
+
return yield* new GraphqlIntrospectionError({
|
|
215
|
+
message: "Introspection response missing __schema"
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return json.data;
|
|
219
|
+
});
|
|
220
|
+
var parseIntrospectionJson = (text) => Effect2.try({
|
|
221
|
+
try: () => {
|
|
222
|
+
const parsed = JSON.parse(text);
|
|
223
|
+
const result = parsed.data ?? parsed;
|
|
224
|
+
if (!result.__schema) {
|
|
225
|
+
throw new Error("Missing __schema in introspection JSON");
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
228
|
+
},
|
|
229
|
+
catch: (err) => new GraphqlIntrospectionError({
|
|
230
|
+
message: `Failed to parse introspection JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
231
|
+
})
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// src/sdk/extract.ts
|
|
235
|
+
import { Effect as Effect3, Option } from "effect";
|
|
236
|
+
var unwrapTypeName = (ref) => {
|
|
237
|
+
if (ref.name) return ref.name;
|
|
238
|
+
if (ref.ofType) return unwrapTypeName(ref.ofType);
|
|
239
|
+
return "Unknown";
|
|
240
|
+
};
|
|
241
|
+
var isNonNull = (ref) => ref.kind === "NON_NULL";
|
|
242
|
+
var buildDefinitions = (types) => {
|
|
243
|
+
const defs = {};
|
|
244
|
+
for (const [name, type] of types) {
|
|
245
|
+
if (name.startsWith("__")) continue;
|
|
246
|
+
if (type.kind === "INPUT_OBJECT" && type.inputFields) {
|
|
247
|
+
const properties = {};
|
|
248
|
+
const required = [];
|
|
249
|
+
for (const field of type.inputFields) {
|
|
250
|
+
const schema = typeRefToJsonSchema(field.type, types);
|
|
251
|
+
if (field.description) {
|
|
252
|
+
schema.description = field.description;
|
|
253
|
+
}
|
|
254
|
+
properties[field.name] = schema;
|
|
255
|
+
if (isNonNull(field.type)) {
|
|
256
|
+
required.push(field.name);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const def = { type: "object", properties };
|
|
260
|
+
if (required.length > 0) def.required = required;
|
|
261
|
+
if (type.description) def.description = type.description;
|
|
262
|
+
defs[name] = def;
|
|
263
|
+
}
|
|
264
|
+
if (type.kind === "ENUM" && type.enumValues) {
|
|
265
|
+
defs[name] = {
|
|
266
|
+
type: "string",
|
|
267
|
+
enum: type.enumValues.map((v) => v.name),
|
|
268
|
+
...type.description ? { description: type.description } : {}
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return defs;
|
|
273
|
+
};
|
|
274
|
+
var typeRefToJsonSchema = (ref, types) => {
|
|
275
|
+
switch (ref.kind) {
|
|
276
|
+
case "NON_NULL":
|
|
277
|
+
return ref.ofType ? typeRefToJsonSchema(ref.ofType, types) : {};
|
|
278
|
+
case "LIST":
|
|
279
|
+
return {
|
|
280
|
+
type: "array",
|
|
281
|
+
items: ref.ofType ? typeRefToJsonSchema(ref.ofType, types) : {}
|
|
282
|
+
};
|
|
283
|
+
case "SCALAR":
|
|
284
|
+
return scalarToJsonSchema(ref.name ?? "String");
|
|
285
|
+
case "ENUM":
|
|
286
|
+
return ref.name ? { $ref: `#/$defs/${ref.name}` } : { type: "string" };
|
|
287
|
+
case "INPUT_OBJECT":
|
|
288
|
+
return ref.name ? { $ref: `#/$defs/${ref.name}` } : { type: "object" };
|
|
289
|
+
case "OBJECT":
|
|
290
|
+
case "INTERFACE":
|
|
291
|
+
case "UNION":
|
|
292
|
+
return { type: "object" };
|
|
293
|
+
default:
|
|
294
|
+
return {};
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
var scalarToJsonSchema = (name) => {
|
|
298
|
+
switch (name) {
|
|
299
|
+
case "String":
|
|
300
|
+
case "ID":
|
|
301
|
+
return { type: "string" };
|
|
302
|
+
case "Int":
|
|
303
|
+
return { type: "integer" };
|
|
304
|
+
case "Float":
|
|
305
|
+
return { type: "number" };
|
|
306
|
+
case "Boolean":
|
|
307
|
+
return { type: "boolean" };
|
|
308
|
+
default:
|
|
309
|
+
return { type: "string", description: `Custom scalar: ${name}` };
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
var buildInputSchema = (args, types) => {
|
|
313
|
+
if (args.length === 0) return void 0;
|
|
314
|
+
const properties = {};
|
|
315
|
+
const required = [];
|
|
316
|
+
for (const arg of args) {
|
|
317
|
+
const schema = typeRefToJsonSchema(arg.type, types);
|
|
318
|
+
if (arg.description) {
|
|
319
|
+
schema.description = arg.description;
|
|
320
|
+
}
|
|
321
|
+
properties[arg.name] = schema;
|
|
322
|
+
if (isNonNull(arg.type)) {
|
|
323
|
+
required.push(arg.name);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const inputSchema = {
|
|
327
|
+
type: "object",
|
|
328
|
+
properties
|
|
329
|
+
};
|
|
330
|
+
if (required.length > 0) inputSchema.required = required;
|
|
331
|
+
return inputSchema;
|
|
332
|
+
};
|
|
333
|
+
var formatTypeRef = (ref) => {
|
|
334
|
+
switch (ref.kind) {
|
|
335
|
+
case "NON_NULL":
|
|
336
|
+
return ref.ofType ? `${formatTypeRef(ref.ofType)}!` : "Unknown!";
|
|
337
|
+
case "LIST":
|
|
338
|
+
return ref.ofType ? `[${formatTypeRef(ref.ofType)}]` : "[Unknown]";
|
|
339
|
+
default:
|
|
340
|
+
return ref.name ?? "Unknown";
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
var extractFields = (_schema, kind, typeName, types) => {
|
|
344
|
+
if (!typeName) return [];
|
|
345
|
+
const type = types.get(typeName);
|
|
346
|
+
if (!type?.fields) return [];
|
|
347
|
+
return type.fields.filter((f) => !f.name.startsWith("__")).map((field) => {
|
|
348
|
+
const args = field.args.map(
|
|
349
|
+
(arg) => new GraphqlArgument({
|
|
350
|
+
name: arg.name,
|
|
351
|
+
typeName: formatTypeRef(arg.type),
|
|
352
|
+
required: isNonNull(arg.type),
|
|
353
|
+
description: arg.description ? Option.some(arg.description) : Option.none()
|
|
354
|
+
})
|
|
355
|
+
);
|
|
356
|
+
const inputSchema = buildInputSchema(field.args, types);
|
|
357
|
+
return new ExtractedField({
|
|
358
|
+
fieldName: field.name,
|
|
359
|
+
kind,
|
|
360
|
+
description: field.description ? Option.some(field.description) : Option.none(),
|
|
361
|
+
arguments: args,
|
|
362
|
+
inputSchema: inputSchema ? Option.some(inputSchema) : Option.none(),
|
|
363
|
+
returnTypeName: unwrapTypeName(field.type)
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
};
|
|
367
|
+
var extract = (introspection) => Effect3.try({
|
|
368
|
+
try: () => {
|
|
369
|
+
const schema = introspection.__schema;
|
|
370
|
+
const typeMap = /* @__PURE__ */ new Map();
|
|
371
|
+
for (const t of schema.types) {
|
|
372
|
+
typeMap.set(t.name, t);
|
|
373
|
+
}
|
|
374
|
+
const definitions = buildDefinitions(typeMap);
|
|
375
|
+
const queryFields = extractFields(schema, "query", schema.queryType?.name, typeMap);
|
|
376
|
+
const mutationFields = extractFields(schema, "mutation", schema.mutationType?.name, typeMap);
|
|
377
|
+
return {
|
|
378
|
+
result: new ExtractionResult({
|
|
379
|
+
schemaName: Option.none(),
|
|
380
|
+
fields: [...queryFields, ...mutationFields]
|
|
381
|
+
}),
|
|
382
|
+
definitions
|
|
383
|
+
};
|
|
384
|
+
},
|
|
385
|
+
catch: (err) => new GraphqlExtractionError({
|
|
386
|
+
message: `Failed to extract GraphQL schema: ${err instanceof Error ? err.message : String(err)}`
|
|
387
|
+
})
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// src/sdk/invoke.ts
|
|
391
|
+
import { Effect as Effect4, Option as Option2 } from "effect";
|
|
392
|
+
import { HttpClient as HttpClient2, HttpClientRequest as HttpClientRequest2 } from "effect/unstable/http";
|
|
393
|
+
import { resolveSecretBackedMap } from "@executor-js/sdk/core";
|
|
394
|
+
var resolveHeaders = (headers, secrets) => {
|
|
395
|
+
const entries = Object.entries(headers);
|
|
396
|
+
const secretCount = entries.reduce(
|
|
397
|
+
(acc, [, value]) => typeof value === "string" ? acc : acc + 1,
|
|
398
|
+
0
|
|
399
|
+
);
|
|
400
|
+
return resolveSecretBackedMap({
|
|
401
|
+
values: headers,
|
|
402
|
+
getSecret: (secretId) => secrets.get(secretId).pipe(Effect4.catch(() => Effect4.succeed(null))),
|
|
403
|
+
missing: "drop",
|
|
404
|
+
onMissing: (name) => new GraphqlInvocationError({
|
|
405
|
+
message: `Missing secret for header "${name}"`,
|
|
406
|
+
statusCode: Option2.none()
|
|
407
|
+
})
|
|
408
|
+
}).pipe(
|
|
409
|
+
Effect4.catch(() => Effect4.succeed(void 0)),
|
|
410
|
+
Effect4.map((resolved) => resolved ?? {}),
|
|
411
|
+
Effect4.withSpan("plugin.graphql.secret.resolve", {
|
|
412
|
+
attributes: {
|
|
413
|
+
"plugin.graphql.headers.total": entries.length,
|
|
414
|
+
"plugin.graphql.headers.secret_count": secretCount
|
|
415
|
+
}
|
|
416
|
+
})
|
|
417
|
+
);
|
|
418
|
+
};
|
|
419
|
+
var endpointWithQueryParams = (endpoint, queryParams) => {
|
|
420
|
+
if (Object.keys(queryParams).length === 0) return endpoint;
|
|
421
|
+
const url = new URL(endpoint);
|
|
422
|
+
for (const [name, value] of Object.entries(queryParams)) {
|
|
423
|
+
url.searchParams.set(name, value);
|
|
424
|
+
}
|
|
425
|
+
return url.toString();
|
|
426
|
+
};
|
|
427
|
+
var isJsonContentType = (ct) => {
|
|
428
|
+
if (!ct) return false;
|
|
429
|
+
const normalized = ct.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
430
|
+
return normalized === "application/json" || normalized.includes("+json") || normalized.includes("json");
|
|
431
|
+
};
|
|
432
|
+
var invoke = Effect4.fn("GraphQL.invoke")(function* (operation, args, endpoint, resolvedHeaders, resolvedQueryParams = {}) {
|
|
433
|
+
const client = yield* HttpClient2.HttpClient;
|
|
434
|
+
const requestEndpoint = endpointWithQueryParams(endpoint, resolvedQueryParams);
|
|
435
|
+
yield* Effect4.annotateCurrentSpan({
|
|
436
|
+
"http.method": "POST",
|
|
437
|
+
"http.url": requestEndpoint,
|
|
438
|
+
"plugin.graphql.endpoint": endpoint,
|
|
439
|
+
"plugin.graphql.operation_kind": operation.kind,
|
|
440
|
+
"plugin.graphql.field_name": operation.fieldName,
|
|
441
|
+
"plugin.graphql.headers.resolved_count": Object.keys(resolvedHeaders).length,
|
|
442
|
+
"plugin.graphql.query_params.resolved_count": Object.keys(resolvedQueryParams).length
|
|
443
|
+
});
|
|
444
|
+
const variables = {};
|
|
445
|
+
for (const varName of operation.variableNames) {
|
|
446
|
+
if (args[varName] !== void 0) {
|
|
447
|
+
variables[varName] = args[varName];
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (typeof args.variables === "object" && args.variables !== null) {
|
|
451
|
+
Object.assign(variables, args.variables);
|
|
452
|
+
}
|
|
453
|
+
let request = HttpClientRequest2.post(requestEndpoint).pipe(
|
|
454
|
+
HttpClientRequest2.setHeader("Content-Type", "application/json"),
|
|
455
|
+
HttpClientRequest2.bodyJsonUnsafe({
|
|
456
|
+
query: operation.operationString,
|
|
457
|
+
variables: Object.keys(variables).length > 0 ? variables : void 0
|
|
458
|
+
})
|
|
459
|
+
);
|
|
460
|
+
for (const [name, value] of Object.entries(resolvedHeaders)) {
|
|
461
|
+
request = HttpClientRequest2.setHeader(request, name, value);
|
|
462
|
+
}
|
|
463
|
+
const response = yield* client.execute(request).pipe(
|
|
464
|
+
Effect4.mapError(
|
|
465
|
+
(err) => new GraphqlInvocationError({
|
|
466
|
+
message: `GraphQL request failed: ${err.message}`,
|
|
467
|
+
statusCode: Option2.none(),
|
|
468
|
+
cause: err
|
|
469
|
+
})
|
|
470
|
+
)
|
|
471
|
+
);
|
|
472
|
+
const status = response.status;
|
|
473
|
+
const contentType = response.headers["content-type"] ?? null;
|
|
474
|
+
const body = isJsonContentType(contentType) ? yield* response.json.pipe(Effect4.catch(() => response.text)) : yield* response.text;
|
|
475
|
+
const gqlBody = body;
|
|
476
|
+
const hasErrors = Array.isArray(gqlBody?.errors) && gqlBody.errors.length > 0;
|
|
477
|
+
yield* Effect4.annotateCurrentSpan({
|
|
478
|
+
"http.status_code": status,
|
|
479
|
+
"plugin.graphql.has_errors": hasErrors,
|
|
480
|
+
"plugin.graphql.error_count": hasErrors ? gqlBody.errors.length : 0
|
|
481
|
+
});
|
|
482
|
+
return new InvocationResult({
|
|
483
|
+
status,
|
|
484
|
+
data: gqlBody?.data ?? null,
|
|
485
|
+
errors: hasErrors ? gqlBody.errors : null
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
var invokeWithLayer = (operation, args, endpoint, resolvedHeaders, resolvedQueryParams, httpClientLayer) => invoke(operation, args, endpoint, resolvedHeaders, resolvedQueryParams).pipe(
|
|
489
|
+
Effect4.provide(httpClientLayer),
|
|
490
|
+
Effect4.mapError(
|
|
491
|
+
(err) => err instanceof GraphqlInvocationError ? err : new GraphqlInvocationError({
|
|
492
|
+
message: err instanceof Error ? err.message : String(err),
|
|
493
|
+
statusCode: Option2.none(),
|
|
494
|
+
cause: err
|
|
495
|
+
})
|
|
496
|
+
),
|
|
497
|
+
Effect4.withSpan("plugin.graphql.invoke", {
|
|
498
|
+
attributes: {
|
|
499
|
+
"plugin.graphql.endpoint": endpoint,
|
|
500
|
+
"plugin.graphql.operation_kind": operation.kind,
|
|
501
|
+
"plugin.graphql.field_name": operation.fieldName
|
|
502
|
+
}
|
|
503
|
+
})
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
// src/sdk/store.ts
|
|
507
|
+
import { Effect as Effect5 } from "effect";
|
|
508
|
+
import {
|
|
509
|
+
defineSchema
|
|
510
|
+
} from "@executor-js/sdk/core";
|
|
511
|
+
var graphqlSchema = defineSchema({
|
|
512
|
+
graphql_source: {
|
|
513
|
+
fields: {
|
|
514
|
+
id: { type: "string", required: true },
|
|
515
|
+
scope_id: { type: "string", required: true, index: true },
|
|
516
|
+
name: { type: "string", required: true },
|
|
517
|
+
endpoint: { type: "string", required: true },
|
|
518
|
+
headers: { type: "json", required: false },
|
|
519
|
+
query_params: { type: "json", required: false },
|
|
520
|
+
auth: { type: "json", required: false }
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
graphql_operation: {
|
|
524
|
+
fields: {
|
|
525
|
+
id: { type: "string", required: true },
|
|
526
|
+
scope_id: { type: "string", required: true, index: true },
|
|
527
|
+
source_id: { type: "string", required: true, index: true },
|
|
528
|
+
binding: { type: "json", required: true }
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
var decodeBinding = (value) => {
|
|
533
|
+
const data = typeof value === "string" ? JSON.parse(value) : value;
|
|
534
|
+
return new OperationBinding({
|
|
535
|
+
kind: data.kind,
|
|
536
|
+
fieldName: data.fieldName,
|
|
537
|
+
operationString: data.operationString,
|
|
538
|
+
variableNames: [...data.variableNames]
|
|
539
|
+
});
|
|
540
|
+
};
|
|
541
|
+
var encodeBinding = (binding) => ({
|
|
542
|
+
kind: binding.kind,
|
|
543
|
+
fieldName: binding.fieldName,
|
|
544
|
+
operationString: binding.operationString,
|
|
545
|
+
variableNames: [...binding.variableNames]
|
|
546
|
+
});
|
|
547
|
+
var toJsonRecord = (value) => value;
|
|
548
|
+
var decodeHeaders = (value) => {
|
|
549
|
+
if (value == null) return {};
|
|
550
|
+
if (typeof value === "string")
|
|
551
|
+
return JSON.parse(value);
|
|
552
|
+
return value;
|
|
553
|
+
};
|
|
554
|
+
var decodeQueryParams = (value) => {
|
|
555
|
+
if (value == null) return {};
|
|
556
|
+
if (typeof value === "string")
|
|
557
|
+
return JSON.parse(value);
|
|
558
|
+
return value;
|
|
559
|
+
};
|
|
560
|
+
var decodeAuth = (value) => {
|
|
561
|
+
if (value == null) return { kind: "none" };
|
|
562
|
+
const parsed = typeof value === "string" ? JSON.parse(value) : value;
|
|
563
|
+
return parsed?.kind === "oauth2" && typeof parsed.connectionId === "string" ? { kind: "oauth2", connectionId: parsed.connectionId } : { kind: "none" };
|
|
564
|
+
};
|
|
565
|
+
var makeDefaultGraphqlStore = ({
|
|
566
|
+
adapter: db
|
|
567
|
+
}) => {
|
|
568
|
+
const rowToSource = (row) => ({
|
|
569
|
+
namespace: row.id,
|
|
570
|
+
scope: row.scope_id,
|
|
571
|
+
name: row.name,
|
|
572
|
+
endpoint: row.endpoint,
|
|
573
|
+
headers: decodeHeaders(row.headers),
|
|
574
|
+
queryParams: decodeQueryParams(row.query_params),
|
|
575
|
+
auth: decodeAuth(row.auth)
|
|
576
|
+
});
|
|
577
|
+
const rowToOperation = (row) => ({
|
|
578
|
+
toolId: row.id,
|
|
579
|
+
sourceId: row.source_id,
|
|
580
|
+
binding: decodeBinding(row.binding)
|
|
581
|
+
});
|
|
582
|
+
const deleteSource = (namespace, scope) => Effect5.gen(function* () {
|
|
583
|
+
yield* db.deleteMany({
|
|
584
|
+
model: "graphql_operation",
|
|
585
|
+
where: [
|
|
586
|
+
{ field: "source_id", value: namespace },
|
|
587
|
+
{ field: "scope_id", value: scope }
|
|
588
|
+
]
|
|
589
|
+
});
|
|
590
|
+
yield* db.delete({
|
|
591
|
+
model: "graphql_source",
|
|
592
|
+
where: [
|
|
593
|
+
{ field: "id", value: namespace },
|
|
594
|
+
{ field: "scope_id", value: scope }
|
|
595
|
+
]
|
|
596
|
+
});
|
|
597
|
+
});
|
|
598
|
+
return {
|
|
599
|
+
upsertSource: (input, operations) => Effect5.gen(function* () {
|
|
600
|
+
yield* deleteSource(input.namespace, input.scope);
|
|
601
|
+
yield* db.create({
|
|
602
|
+
model: "graphql_source",
|
|
603
|
+
data: {
|
|
604
|
+
id: input.namespace,
|
|
605
|
+
scope_id: input.scope,
|
|
606
|
+
name: input.name,
|
|
607
|
+
endpoint: input.endpoint,
|
|
608
|
+
headers: input.headers,
|
|
609
|
+
query_params: input.queryParams,
|
|
610
|
+
auth: toJsonRecord(input.auth)
|
|
611
|
+
},
|
|
612
|
+
forceAllowId: true
|
|
613
|
+
});
|
|
614
|
+
if (operations.length > 0) {
|
|
615
|
+
yield* db.createMany({
|
|
616
|
+
model: "graphql_operation",
|
|
617
|
+
data: operations.map((op) => ({
|
|
618
|
+
id: op.toolId,
|
|
619
|
+
scope_id: input.scope,
|
|
620
|
+
source_id: op.sourceId,
|
|
621
|
+
binding: toJsonRecord(encodeBinding(op.binding))
|
|
622
|
+
})),
|
|
623
|
+
forceAllowId: true
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
}),
|
|
627
|
+
updateSourceMeta: (namespace, scope, patch) => Effect5.gen(function* () {
|
|
628
|
+
const existing = yield* db.findOne({
|
|
629
|
+
model: "graphql_source",
|
|
630
|
+
where: [
|
|
631
|
+
{ field: "id", value: namespace },
|
|
632
|
+
{ field: "scope_id", value: scope }
|
|
633
|
+
]
|
|
634
|
+
});
|
|
635
|
+
if (!existing) return;
|
|
636
|
+
const update = {};
|
|
637
|
+
if (patch.name !== void 0) update.name = patch.name;
|
|
638
|
+
if (patch.endpoint !== void 0) update.endpoint = patch.endpoint;
|
|
639
|
+
if (patch.headers !== void 0) {
|
|
640
|
+
update.headers = patch.headers;
|
|
641
|
+
}
|
|
642
|
+
if (patch.queryParams !== void 0) {
|
|
643
|
+
update.query_params = patch.queryParams;
|
|
644
|
+
}
|
|
645
|
+
if (patch.auth !== void 0) {
|
|
646
|
+
update.auth = toJsonRecord(patch.auth);
|
|
647
|
+
}
|
|
648
|
+
if (Object.keys(update).length === 0) return;
|
|
649
|
+
yield* db.update({
|
|
650
|
+
model: "graphql_source",
|
|
651
|
+
where: [
|
|
652
|
+
{ field: "id", value: namespace },
|
|
653
|
+
{ field: "scope_id", value: scope }
|
|
654
|
+
],
|
|
655
|
+
update
|
|
656
|
+
});
|
|
657
|
+
}),
|
|
658
|
+
getSource: (namespace, scope) => db.findOne({
|
|
659
|
+
model: "graphql_source",
|
|
660
|
+
where: [
|
|
661
|
+
{ field: "id", value: namespace },
|
|
662
|
+
{ field: "scope_id", value: scope }
|
|
663
|
+
]
|
|
664
|
+
}).pipe(Effect5.map((row) => row ? rowToSource(row) : null)),
|
|
665
|
+
listSources: () => db.findMany({ model: "graphql_source" }).pipe(Effect5.map((rows) => rows.map(rowToSource))),
|
|
666
|
+
getOperationByToolId: (toolId, scope) => db.findOne({
|
|
667
|
+
model: "graphql_operation",
|
|
668
|
+
where: [
|
|
669
|
+
{ field: "id", value: toolId },
|
|
670
|
+
{ field: "scope_id", value: scope }
|
|
671
|
+
]
|
|
672
|
+
}).pipe(Effect5.map((row) => row ? rowToOperation(row) : null)),
|
|
673
|
+
listOperationsBySource: (sourceId, scope) => db.findMany({
|
|
674
|
+
model: "graphql_operation",
|
|
675
|
+
where: [
|
|
676
|
+
{ field: "source_id", value: sourceId },
|
|
677
|
+
{ field: "scope_id", value: scope }
|
|
678
|
+
]
|
|
679
|
+
}).pipe(Effect5.map((rows) => rows.map(rowToOperation))),
|
|
680
|
+
removeSource: (namespace, scope) => deleteSource(namespace, scope)
|
|
681
|
+
};
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
// src/sdk/plugin.ts
|
|
685
|
+
import { Effect as Effect7, Option as Option3 } from "effect";
|
|
686
|
+
import { FetchHttpClient } from "effect/unstable/http";
|
|
687
|
+
|
|
688
|
+
// src/api/group.ts
|
|
689
|
+
import { HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi";
|
|
690
|
+
import { Schema as Schema3 } from "effect";
|
|
691
|
+
import { ScopeId } from "@executor-js/sdk/core";
|
|
692
|
+
import { InternalError } from "@executor-js/api";
|
|
693
|
+
var StoredSourceSchema = Schema3.Struct({
|
|
694
|
+
namespace: Schema3.String,
|
|
695
|
+
name: Schema3.String,
|
|
696
|
+
endpoint: Schema3.String,
|
|
697
|
+
headers: Schema3.Record(Schema3.String, HeaderValue),
|
|
698
|
+
queryParams: Schema3.Record(Schema3.String, HeaderValue),
|
|
699
|
+
auth: GraphqlSourceAuth
|
|
700
|
+
});
|
|
701
|
+
var ScopeParams = {
|
|
702
|
+
scopeId: ScopeId
|
|
703
|
+
};
|
|
704
|
+
var SourceParams = {
|
|
705
|
+
scopeId: ScopeId,
|
|
706
|
+
namespace: Schema3.String
|
|
707
|
+
};
|
|
708
|
+
var AddSourcePayload = Schema3.Struct({
|
|
709
|
+
endpoint: Schema3.String,
|
|
710
|
+
name: Schema3.optional(Schema3.String),
|
|
711
|
+
introspectionJson: Schema3.optional(Schema3.String),
|
|
712
|
+
namespace: Schema3.optional(Schema3.String),
|
|
713
|
+
headers: Schema3.optional(Schema3.Record(Schema3.String, Schema3.Unknown)),
|
|
714
|
+
queryParams: Schema3.optional(Schema3.Record(Schema3.String, Schema3.Unknown)),
|
|
715
|
+
auth: Schema3.optional(GraphqlSourceAuth)
|
|
716
|
+
});
|
|
717
|
+
var UpdateSourcePayload = Schema3.Struct({
|
|
718
|
+
name: Schema3.optional(Schema3.String),
|
|
719
|
+
endpoint: Schema3.optional(Schema3.String),
|
|
720
|
+
headers: Schema3.optional(Schema3.Record(Schema3.String, Schema3.Unknown)),
|
|
721
|
+
queryParams: Schema3.optional(Schema3.Record(Schema3.String, Schema3.Unknown)),
|
|
722
|
+
auth: Schema3.optional(GraphqlSourceAuth)
|
|
723
|
+
});
|
|
724
|
+
var UpdateSourceResponse = Schema3.Struct({
|
|
725
|
+
updated: Schema3.Boolean
|
|
726
|
+
});
|
|
727
|
+
var AddSourceResponse = Schema3.Struct({
|
|
728
|
+
toolCount: Schema3.Number,
|
|
729
|
+
namespace: Schema3.String
|
|
730
|
+
});
|
|
731
|
+
var IntrospectionError = GraphqlIntrospectionError.annotate(
|
|
732
|
+
{ httpApiStatus: 400 }
|
|
733
|
+
);
|
|
734
|
+
var ExtractionError = GraphqlExtractionError.annotate(
|
|
735
|
+
{ httpApiStatus: 400 }
|
|
736
|
+
);
|
|
737
|
+
var GraphqlErrors = [InternalError, IntrospectionError, ExtractionError];
|
|
738
|
+
var GraphqlGroup = HttpApiGroup.make("graphql").add(
|
|
739
|
+
HttpApiEndpoint.post("addSource", "/scopes/:scopeId/graphql/sources", {
|
|
740
|
+
params: ScopeParams,
|
|
741
|
+
payload: AddSourcePayload,
|
|
742
|
+
success: AddSourceResponse,
|
|
743
|
+
error: GraphqlErrors
|
|
744
|
+
})
|
|
745
|
+
).add(
|
|
746
|
+
HttpApiEndpoint.get("getSource", "/scopes/:scopeId/graphql/sources/:namespace", {
|
|
747
|
+
params: SourceParams,
|
|
748
|
+
success: Schema3.NullOr(StoredSourceSchema),
|
|
749
|
+
error: GraphqlErrors
|
|
750
|
+
})
|
|
751
|
+
).add(
|
|
752
|
+
HttpApiEndpoint.patch("updateSource", "/scopes/:scopeId/graphql/sources/:namespace", {
|
|
753
|
+
params: SourceParams,
|
|
754
|
+
payload: UpdateSourcePayload,
|
|
755
|
+
success: UpdateSourceResponse,
|
|
756
|
+
error: GraphqlErrors
|
|
757
|
+
})
|
|
758
|
+
);
|
|
759
|
+
|
|
760
|
+
// src/api/handlers.ts
|
|
761
|
+
import { HttpApiBuilder } from "effect/unstable/httpapi";
|
|
762
|
+
import { Context, Effect as Effect6 } from "effect";
|
|
763
|
+
import { addGroup, capture } from "@executor-js/api";
|
|
764
|
+
var GraphqlExtensionService = class extends Context.Service()("GraphqlExtensionService") {
|
|
765
|
+
};
|
|
766
|
+
var ExecutorApiWithGraphql = addGroup(GraphqlGroup);
|
|
767
|
+
var GraphqlHandlers = HttpApiBuilder.group(
|
|
768
|
+
ExecutorApiWithGraphql,
|
|
769
|
+
"graphql",
|
|
770
|
+
(handlers) => handlers.handle(
|
|
771
|
+
"addSource",
|
|
772
|
+
({ params: path, payload }) => capture(
|
|
773
|
+
Effect6.gen(function* () {
|
|
774
|
+
const ext = yield* GraphqlExtensionService;
|
|
775
|
+
const result = yield* ext.addSource({
|
|
776
|
+
endpoint: payload.endpoint,
|
|
777
|
+
scope: path.scopeId,
|
|
778
|
+
name: payload.name,
|
|
779
|
+
introspectionJson: payload.introspectionJson,
|
|
780
|
+
namespace: payload.namespace,
|
|
781
|
+
headers: payload.headers,
|
|
782
|
+
queryParams: payload.queryParams,
|
|
783
|
+
auth: payload.auth
|
|
784
|
+
});
|
|
785
|
+
return {
|
|
786
|
+
toolCount: result.toolCount,
|
|
787
|
+
namespace: result.namespace
|
|
788
|
+
};
|
|
789
|
+
})
|
|
790
|
+
)
|
|
791
|
+
).handle(
|
|
792
|
+
"getSource",
|
|
793
|
+
({ params: path }) => capture(
|
|
794
|
+
Effect6.gen(function* () {
|
|
795
|
+
const ext = yield* GraphqlExtensionService;
|
|
796
|
+
return yield* ext.getSource(path.namespace, path.scopeId);
|
|
797
|
+
})
|
|
798
|
+
)
|
|
799
|
+
).handle(
|
|
800
|
+
"updateSource",
|
|
801
|
+
({ params: path, payload }) => capture(
|
|
802
|
+
Effect6.gen(function* () {
|
|
803
|
+
const ext = yield* GraphqlExtensionService;
|
|
804
|
+
yield* ext.updateSource(path.namespace, path.scopeId, {
|
|
805
|
+
name: payload.name,
|
|
806
|
+
endpoint: payload.endpoint,
|
|
807
|
+
headers: payload.headers,
|
|
808
|
+
queryParams: payload.queryParams,
|
|
809
|
+
auth: payload.auth
|
|
810
|
+
});
|
|
811
|
+
return { updated: true };
|
|
812
|
+
})
|
|
813
|
+
)
|
|
814
|
+
)
|
|
815
|
+
);
|
|
816
|
+
|
|
817
|
+
// src/sdk/plugin.ts
|
|
818
|
+
import {
|
|
819
|
+
definePlugin,
|
|
820
|
+
SourceDetectionResult
|
|
821
|
+
} from "@executor-js/sdk/core";
|
|
822
|
+
import {
|
|
823
|
+
headersToConfigValues
|
|
824
|
+
} from "@executor-js/config";
|
|
825
|
+
var namespaceFromEndpoint = (endpoint) => {
|
|
826
|
+
try {
|
|
827
|
+
const url = new URL(endpoint);
|
|
828
|
+
return url.hostname.replace(/[^a-z0-9]+/gi, "_").toLowerCase();
|
|
829
|
+
} catch {
|
|
830
|
+
return "graphql";
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
var formatTypeRef2 = (ref) => {
|
|
834
|
+
switch (ref.kind) {
|
|
835
|
+
case "NON_NULL":
|
|
836
|
+
return ref.ofType ? `${formatTypeRef2(ref.ofType)}!` : "Unknown!";
|
|
837
|
+
case "LIST":
|
|
838
|
+
return ref.ofType ? `[${formatTypeRef2(ref.ofType)}]` : "[Unknown]";
|
|
839
|
+
default:
|
|
840
|
+
return ref.name ?? "Unknown";
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
var unwrapTypeName2 = (ref) => {
|
|
844
|
+
if (ref.name) return ref.name;
|
|
845
|
+
if (ref.ofType) return unwrapTypeName2(ref.ofType);
|
|
846
|
+
return "Unknown";
|
|
847
|
+
};
|
|
848
|
+
var buildSelectionSet = (ref, types, depth, seen) => {
|
|
849
|
+
if (depth > 2) return "";
|
|
850
|
+
const leafName = unwrapTypeName2(ref);
|
|
851
|
+
if (seen.has(leafName)) return "";
|
|
852
|
+
const objectType = types.get(leafName);
|
|
853
|
+
if (!objectType?.fields) return "";
|
|
854
|
+
const kind = objectType.kind;
|
|
855
|
+
if (kind === "SCALAR" || kind === "ENUM") return "";
|
|
856
|
+
seen.add(leafName);
|
|
857
|
+
const subFields = objectType.fields.filter((f) => !f.name.startsWith("__")).slice(0, 12).map((f) => {
|
|
858
|
+
const sub = buildSelectionSet(f.type, types, depth + 1, seen);
|
|
859
|
+
return sub ? `${f.name} ${sub}` : f.name;
|
|
860
|
+
});
|
|
861
|
+
seen.delete(leafName);
|
|
862
|
+
return subFields.length > 0 ? `{ ${subFields.join(" ")} }` : "";
|
|
863
|
+
};
|
|
864
|
+
var buildOperationStringForField = (kind, field, types) => {
|
|
865
|
+
const opType = kind === "query" ? "query" : "mutation";
|
|
866
|
+
const varDefs = field.args.map((arg) => {
|
|
867
|
+
const typeName = formatTypeRef2(arg.type);
|
|
868
|
+
return `$${arg.name}: ${typeName}`;
|
|
869
|
+
});
|
|
870
|
+
const argPasses = field.args.map((arg) => `${arg.name}: $${arg.name}`);
|
|
871
|
+
const selectionSet = buildSelectionSet(field.type, types, 0, /* @__PURE__ */ new Set());
|
|
872
|
+
const varDefsStr = varDefs.length > 0 ? `(${varDefs.join(", ")})` : "";
|
|
873
|
+
const argPassStr = argPasses.length > 0 ? `(${argPasses.join(", ")})` : "";
|
|
874
|
+
return `${opType}${varDefsStr} { ${field.name}${argPassStr}${selectionSet ? ` ${selectionSet}` : ""} }`;
|
|
875
|
+
};
|
|
876
|
+
var prepareOperations = (fields, introspection) => {
|
|
877
|
+
const typeMap = /* @__PURE__ */ new Map();
|
|
878
|
+
for (const t of introspection.__schema.types) {
|
|
879
|
+
typeMap.set(t.name, t);
|
|
880
|
+
}
|
|
881
|
+
const fieldMap = /* @__PURE__ */ new Map();
|
|
882
|
+
const schema = introspection.__schema;
|
|
883
|
+
for (const rootKind of ["query", "mutation"]) {
|
|
884
|
+
const typeName = rootKind === "query" ? schema.queryType?.name : schema.mutationType?.name;
|
|
885
|
+
if (!typeName) continue;
|
|
886
|
+
const rootType = typeMap.get(typeName);
|
|
887
|
+
if (!rootType?.fields) continue;
|
|
888
|
+
for (const f of rootType.fields) {
|
|
889
|
+
if (!f.name.startsWith("__")) {
|
|
890
|
+
fieldMap.set(`${rootKind}.${f.name}`, { kind: rootKind, field: f });
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return fields.map((extracted) => {
|
|
895
|
+
const prefix = extracted.kind === "mutation" ? "mutation" : "query";
|
|
896
|
+
const toolPath = `${prefix}.${extracted.fieldName}`;
|
|
897
|
+
const description = Option3.getOrElse(
|
|
898
|
+
extracted.description,
|
|
899
|
+
() => `GraphQL ${extracted.kind}: ${extracted.fieldName} -> ${extracted.returnTypeName}`
|
|
900
|
+
);
|
|
901
|
+
const key = `${extracted.kind}.${extracted.fieldName}`;
|
|
902
|
+
const entry = fieldMap.get(key);
|
|
903
|
+
const operationString = entry ? buildOperationStringForField(entry.kind, entry.field, typeMap) : `${extracted.kind} { ${extracted.fieldName} }`;
|
|
904
|
+
const binding = new OperationBinding({
|
|
905
|
+
kind: extracted.kind,
|
|
906
|
+
fieldName: extracted.fieldName,
|
|
907
|
+
operationString,
|
|
908
|
+
variableNames: extracted.arguments.map((a) => a.name)
|
|
909
|
+
});
|
|
910
|
+
return {
|
|
911
|
+
toolPath,
|
|
912
|
+
description,
|
|
913
|
+
inputSchema: Option3.getOrUndefined(extracted.inputSchema),
|
|
914
|
+
binding
|
|
915
|
+
};
|
|
916
|
+
});
|
|
917
|
+
};
|
|
918
|
+
var annotationsFor = (binding) => {
|
|
919
|
+
if (binding.kind === "mutation") {
|
|
920
|
+
return {
|
|
921
|
+
requiresApproval: true,
|
|
922
|
+
approvalDescription: `mutation ${binding.fieldName}`
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
return {};
|
|
926
|
+
};
|
|
927
|
+
var toGraphqlConfigEntry = (namespace, config) => ({
|
|
928
|
+
kind: "graphql",
|
|
929
|
+
endpoint: config.endpoint,
|
|
930
|
+
introspectionJson: config.introspectionJson,
|
|
931
|
+
namespace,
|
|
932
|
+
headers: headersToConfigValues(config.headers)
|
|
933
|
+
});
|
|
934
|
+
var graphqlPlugin = definePlugin((options) => {
|
|
935
|
+
const httpClientLayer = options?.httpClientLayer ?? FetchHttpClient.layer;
|
|
936
|
+
return {
|
|
937
|
+
id: "graphql",
|
|
938
|
+
packageName: "@executor-js/plugin-graphql",
|
|
939
|
+
schema: graphqlSchema,
|
|
940
|
+
storage: (deps) => makeDefaultGraphqlStore(deps),
|
|
941
|
+
extension: (ctx) => {
|
|
942
|
+
const resolveConfigValues = (values) => Effect7.gen(function* () {
|
|
943
|
+
if (!values) return void 0;
|
|
944
|
+
const resolved = yield* resolveHeaders(values, ctx.secrets);
|
|
945
|
+
return Object.keys(resolved).length > 0 ? resolved : void 0;
|
|
946
|
+
});
|
|
947
|
+
const resolveOAuthHeader = (auth) => Effect7.gen(function* () {
|
|
948
|
+
if (!auth || auth.kind === "none") return void 0;
|
|
949
|
+
const accessToken = yield* ctx.connections.accessToken(auth.connectionId).pipe(
|
|
950
|
+
Effect7.mapError(
|
|
951
|
+
(err) => new GraphqlIntrospectionError({
|
|
952
|
+
message: `Failed to resolve OAuth connection "${auth.connectionId}": ${"message" in err ? err.message : String(err)}`
|
|
953
|
+
})
|
|
954
|
+
)
|
|
955
|
+
);
|
|
956
|
+
return { Authorization: `Bearer ${accessToken}` };
|
|
957
|
+
});
|
|
958
|
+
const resolveRequestHeaders = (headers, auth) => Effect7.gen(function* () {
|
|
959
|
+
const resolvedHeaders = yield* resolveConfigValues(headers);
|
|
960
|
+
const oauthHeader = yield* resolveOAuthHeader(auth);
|
|
961
|
+
return { ...resolvedHeaders ?? {}, ...oauthHeader ?? {} };
|
|
962
|
+
});
|
|
963
|
+
const addSourceInternal = (config) => ctx.transaction(
|
|
964
|
+
Effect7.gen(function* () {
|
|
965
|
+
let introspectionResult;
|
|
966
|
+
if (config.introspectionJson) {
|
|
967
|
+
introspectionResult = yield* parseIntrospectionJson(
|
|
968
|
+
config.introspectionJson
|
|
969
|
+
);
|
|
970
|
+
} else {
|
|
971
|
+
const resolvedHeaders = yield* resolveRequestHeaders(
|
|
972
|
+
config.headers,
|
|
973
|
+
config.auth
|
|
974
|
+
);
|
|
975
|
+
const resolvedQueryParams = yield* resolveConfigValues(
|
|
976
|
+
config.queryParams
|
|
977
|
+
);
|
|
978
|
+
introspectionResult = yield* introspect(
|
|
979
|
+
config.endpoint,
|
|
980
|
+
Object.keys(resolvedHeaders).length > 0 ? resolvedHeaders : void 0,
|
|
981
|
+
resolvedQueryParams
|
|
982
|
+
).pipe(Effect7.provide(httpClientLayer));
|
|
983
|
+
}
|
|
984
|
+
const { result, definitions } = yield* extract(introspectionResult);
|
|
985
|
+
const namespace = config.namespace ?? namespaceFromEndpoint(config.endpoint);
|
|
986
|
+
const prepared = prepareOperations(
|
|
987
|
+
result.fields,
|
|
988
|
+
introspectionResult
|
|
989
|
+
);
|
|
990
|
+
const displayName = config.name?.trim() || namespace;
|
|
991
|
+
const storedSource = {
|
|
992
|
+
namespace,
|
|
993
|
+
scope: config.scope,
|
|
994
|
+
name: displayName,
|
|
995
|
+
endpoint: config.endpoint,
|
|
996
|
+
headers: config.headers ?? {},
|
|
997
|
+
queryParams: config.queryParams ?? {},
|
|
998
|
+
auth: config.auth ?? { kind: "none" }
|
|
999
|
+
};
|
|
1000
|
+
const storedOps = prepared.map((p) => ({
|
|
1001
|
+
toolId: `${namespace}.${p.toolPath}`,
|
|
1002
|
+
sourceId: namespace,
|
|
1003
|
+
binding: p.binding
|
|
1004
|
+
}));
|
|
1005
|
+
yield* ctx.storage.upsertSource(storedSource, storedOps);
|
|
1006
|
+
yield* ctx.core.sources.register({
|
|
1007
|
+
id: namespace,
|
|
1008
|
+
scope: config.scope,
|
|
1009
|
+
kind: "graphql",
|
|
1010
|
+
name: displayName,
|
|
1011
|
+
url: config.endpoint,
|
|
1012
|
+
canRemove: true,
|
|
1013
|
+
canRefresh: false,
|
|
1014
|
+
canEdit: true,
|
|
1015
|
+
tools: prepared.map((p) => ({
|
|
1016
|
+
name: p.toolPath,
|
|
1017
|
+
description: p.description,
|
|
1018
|
+
inputSchema: p.inputSchema
|
|
1019
|
+
}))
|
|
1020
|
+
});
|
|
1021
|
+
if (Object.keys(definitions).length > 0) {
|
|
1022
|
+
yield* ctx.core.definitions.register({
|
|
1023
|
+
sourceId: namespace,
|
|
1024
|
+
scope: config.scope,
|
|
1025
|
+
definitions
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
return { toolCount: prepared.length, namespace };
|
|
1029
|
+
})
|
|
1030
|
+
);
|
|
1031
|
+
const configFile = options?.configFile;
|
|
1032
|
+
return {
|
|
1033
|
+
addSource: (config) => addSourceInternal(config).pipe(
|
|
1034
|
+
Effect7.tap(
|
|
1035
|
+
(result) => configFile ? configFile.upsertSource(
|
|
1036
|
+
toGraphqlConfigEntry(result.namespace, config)
|
|
1037
|
+
) : Effect7.void
|
|
1038
|
+
)
|
|
1039
|
+
),
|
|
1040
|
+
removeSource: (namespace, scope) => Effect7.gen(function* () {
|
|
1041
|
+
yield* ctx.transaction(
|
|
1042
|
+
Effect7.gen(function* () {
|
|
1043
|
+
yield* ctx.storage.removeSource(namespace, scope);
|
|
1044
|
+
yield* ctx.core.sources.unregister(namespace);
|
|
1045
|
+
})
|
|
1046
|
+
);
|
|
1047
|
+
if (configFile) {
|
|
1048
|
+
yield* configFile.removeSource(namespace);
|
|
1049
|
+
}
|
|
1050
|
+
}),
|
|
1051
|
+
getSource: (namespace, scope) => ctx.storage.getSource(namespace, scope),
|
|
1052
|
+
updateSource: (namespace, scope, input) => ctx.storage.updateSourceMeta(namespace, scope, {
|
|
1053
|
+
name: input.name?.trim() || void 0,
|
|
1054
|
+
endpoint: input.endpoint,
|
|
1055
|
+
headers: input.headers,
|
|
1056
|
+
queryParams: input.queryParams,
|
|
1057
|
+
auth: input.auth
|
|
1058
|
+
})
|
|
1059
|
+
};
|
|
1060
|
+
},
|
|
1061
|
+
staticSources: (self) => [
|
|
1062
|
+
{
|
|
1063
|
+
id: "graphql",
|
|
1064
|
+
kind: "control",
|
|
1065
|
+
name: "GraphQL",
|
|
1066
|
+
tools: [
|
|
1067
|
+
{
|
|
1068
|
+
name: "addSource",
|
|
1069
|
+
description: "Add a GraphQL endpoint and register its operations as tools",
|
|
1070
|
+
inputSchema: {
|
|
1071
|
+
type: "object",
|
|
1072
|
+
properties: {
|
|
1073
|
+
endpoint: { type: "string" },
|
|
1074
|
+
name: { type: "string" },
|
|
1075
|
+
introspectionJson: { type: "string" },
|
|
1076
|
+
namespace: { type: "string" },
|
|
1077
|
+
headers: { type: "object" },
|
|
1078
|
+
queryParams: { type: "object" },
|
|
1079
|
+
auth: { type: "object" }
|
|
1080
|
+
},
|
|
1081
|
+
required: ["endpoint"]
|
|
1082
|
+
},
|
|
1083
|
+
outputSchema: {
|
|
1084
|
+
type: "object",
|
|
1085
|
+
properties: {
|
|
1086
|
+
toolCount: { type: "number" }
|
|
1087
|
+
},
|
|
1088
|
+
required: ["toolCount"]
|
|
1089
|
+
},
|
|
1090
|
+
// Static-tool callers don't name a scope. Default to the
|
|
1091
|
+
// outermost scope in the executor's stack — for a single-
|
|
1092
|
+
// scope executor that's the only scope; for a per-user
|
|
1093
|
+
// stack `[user, org]` it writes at `org` so the source is
|
|
1094
|
+
// visible across every user.
|
|
1095
|
+
handler: ({ ctx, args }) => self.addSource({
|
|
1096
|
+
...args,
|
|
1097
|
+
scope: ctx.scopes.at(-1).id
|
|
1098
|
+
})
|
|
1099
|
+
}
|
|
1100
|
+
]
|
|
1101
|
+
}
|
|
1102
|
+
],
|
|
1103
|
+
invokeTool: ({ ctx, toolRow, args }) => Effect7.gen(function* () {
|
|
1104
|
+
const toolScope = toolRow.scope_id;
|
|
1105
|
+
const op = yield* ctx.storage.getOperationByToolId(
|
|
1106
|
+
toolRow.id,
|
|
1107
|
+
toolScope
|
|
1108
|
+
);
|
|
1109
|
+
if (!op) {
|
|
1110
|
+
return yield* Effect7.fail(
|
|
1111
|
+
new Error(`No GraphQL operation found for tool "${toolRow.id}"`)
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
const source = yield* ctx.storage.getSource(op.sourceId, toolScope);
|
|
1115
|
+
if (!source) {
|
|
1116
|
+
return yield* Effect7.fail(
|
|
1117
|
+
new Error(`No GraphQL source found for "${op.sourceId}"`)
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
const resolvedHeaders = yield* resolveHeaders(
|
|
1121
|
+
source.headers,
|
|
1122
|
+
ctx.secrets
|
|
1123
|
+
);
|
|
1124
|
+
const resolvedQueryParams = yield* resolveHeaders(
|
|
1125
|
+
source.queryParams,
|
|
1126
|
+
ctx.secrets
|
|
1127
|
+
);
|
|
1128
|
+
if (source.auth.kind === "oauth2") {
|
|
1129
|
+
const accessToken = yield* ctx.connections.accessToken(
|
|
1130
|
+
source.auth.connectionId
|
|
1131
|
+
);
|
|
1132
|
+
resolvedHeaders.Authorization = `Bearer ${accessToken}`;
|
|
1133
|
+
}
|
|
1134
|
+
const result = yield* invokeWithLayer(
|
|
1135
|
+
op.binding,
|
|
1136
|
+
args ?? {},
|
|
1137
|
+
source.endpoint,
|
|
1138
|
+
resolvedHeaders,
|
|
1139
|
+
resolvedQueryParams,
|
|
1140
|
+
httpClientLayer
|
|
1141
|
+
);
|
|
1142
|
+
return result;
|
|
1143
|
+
}),
|
|
1144
|
+
resolveAnnotations: ({ ctx, sourceId, toolRows }) => Effect7.gen(function* () {
|
|
1145
|
+
const scopes = /* @__PURE__ */ new Set();
|
|
1146
|
+
for (const row of toolRows) {
|
|
1147
|
+
scopes.add(row.scope_id);
|
|
1148
|
+
}
|
|
1149
|
+
const entries = yield* Effect7.forEach(
|
|
1150
|
+
[...scopes],
|
|
1151
|
+
(scope) => Effect7.gen(function* () {
|
|
1152
|
+
const ops = yield* ctx.storage.listOperationsBySource(
|
|
1153
|
+
sourceId,
|
|
1154
|
+
scope
|
|
1155
|
+
);
|
|
1156
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1157
|
+
for (const op of ops) byId.set(op.toolId, op.binding);
|
|
1158
|
+
return [scope, byId];
|
|
1159
|
+
}),
|
|
1160
|
+
{ concurrency: "unbounded" }
|
|
1161
|
+
);
|
|
1162
|
+
const byScope = new Map(entries);
|
|
1163
|
+
const out = {};
|
|
1164
|
+
for (const row of toolRows) {
|
|
1165
|
+
const binding = byScope.get(row.scope_id)?.get(row.id);
|
|
1166
|
+
if (binding) out[row.id] = annotationsFor(binding);
|
|
1167
|
+
}
|
|
1168
|
+
return out;
|
|
1169
|
+
}),
|
|
1170
|
+
removeSource: ({ ctx, sourceId, scope }) => ctx.storage.removeSource(sourceId, scope),
|
|
1171
|
+
detect: ({ url }) => Effect7.gen(function* () {
|
|
1172
|
+
const trimmed = url.trim();
|
|
1173
|
+
if (!trimmed) return null;
|
|
1174
|
+
const parsed = yield* Effect7.try({
|
|
1175
|
+
try: () => new URL(trimmed),
|
|
1176
|
+
catch: (cause) => cause
|
|
1177
|
+
}).pipe(
|
|
1178
|
+
Effect7.option
|
|
1179
|
+
);
|
|
1180
|
+
if (Option3.isNone(parsed)) return null;
|
|
1181
|
+
const ok = yield* introspect(trimmed).pipe(
|
|
1182
|
+
Effect7.provide(httpClientLayer),
|
|
1183
|
+
Effect7.map(() => true),
|
|
1184
|
+
Effect7.catch(() => Effect7.succeed(false))
|
|
1185
|
+
);
|
|
1186
|
+
if (!ok) return null;
|
|
1187
|
+
const name = namespaceFromEndpoint(trimmed);
|
|
1188
|
+
return new SourceDetectionResult({
|
|
1189
|
+
kind: "graphql",
|
|
1190
|
+
confidence: "high",
|
|
1191
|
+
endpoint: trimmed,
|
|
1192
|
+
name,
|
|
1193
|
+
namespace: name
|
|
1194
|
+
});
|
|
1195
|
+
}),
|
|
1196
|
+
routes: () => GraphqlGroup,
|
|
1197
|
+
handlers: () => GraphqlHandlers,
|
|
1198
|
+
extensionService: GraphqlExtensionService
|
|
1199
|
+
};
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
export {
|
|
1203
|
+
GraphqlIntrospectionError,
|
|
1204
|
+
GraphqlExtractionError,
|
|
1205
|
+
GraphqlInvocationError,
|
|
1206
|
+
GraphqlOperationKind,
|
|
1207
|
+
GraphqlArgument,
|
|
1208
|
+
ExtractedField,
|
|
1209
|
+
ExtractionResult,
|
|
1210
|
+
OperationBinding,
|
|
1211
|
+
HeaderValue,
|
|
1212
|
+
GraphqlSourceAuth,
|
|
1213
|
+
InvocationConfig,
|
|
1214
|
+
InvocationResult,
|
|
1215
|
+
introspect,
|
|
1216
|
+
parseIntrospectionJson,
|
|
1217
|
+
extract,
|
|
1218
|
+
resolveHeaders,
|
|
1219
|
+
invoke,
|
|
1220
|
+
invokeWithLayer,
|
|
1221
|
+
graphqlSchema,
|
|
1222
|
+
makeDefaultGraphqlStore,
|
|
1223
|
+
graphqlPlugin
|
|
1224
|
+
};
|
|
1225
|
+
//# sourceMappingURL=chunk-EIC5WI6C.js.map
|