@executor-js/plugin-graphql 0.1.0 → 1.4.20
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-3Z3DB4KQ.js +239 -0
- package/dist/AddGraphqlSource-3Z3DB4KQ.js.map +1 -0
- package/dist/EditGraphqlSource-F37RPNKV.js +251 -0
- package/dist/EditGraphqlSource-F37RPNKV.js.map +1 -0
- package/dist/GraphqlSourceSummary-6EYBXVD2.js +70 -0
- package/dist/GraphqlSourceSummary-6EYBXVD2.js.map +1 -0
- package/dist/api/group.d.ts +204 -13
- package/dist/api/handlers.d.ts +51 -3
- package/dist/api/index.d.ts +482 -0
- package/dist/chunk-445ZPXHU.js +148 -0
- package/dist/chunk-445ZPXHU.js.map +1 -0
- package/dist/chunk-EW4Y3KEX.js +182 -0
- package/dist/chunk-EW4Y3KEX.js.map +1 -0
- package/dist/chunk-M4SJY6CB.js +45 -0
- package/dist/chunk-M4SJY6CB.js.map +1 -0
- package/dist/chunk-PO2TPM5B.js +1636 -0
- package/dist/chunk-PO2TPM5B.js.map +1 -0
- package/dist/client.js +75 -0
- package/dist/client.js.map +1 -0
- package/dist/core.js +32 -10
- package/dist/index.js +2 -1
- package/dist/react/GraphqlSourceFields.d.ts +8 -0
- package/dist/react/GraphqlSourceSummary.d.ts +3 -1
- package/dist/react/atoms.d.ts +206 -10
- package/dist/react/client.d.ts +200 -12
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/introspect.d.ts +437 -43
- package/dist/sdk/invoke.d.ts +17 -3
- package/dist/sdk/plugin.d.ts +192 -170
- package/dist/sdk/store.d.ts +84 -11
- package/dist/sdk/types.d.ts +129 -30
- package/dist/testing/index.d.ts +52 -0
- package/dist/testing.js +131 -0
- package/dist/testing.js.map +1 -0
- package/package.json +18 -4
- package/dist/chunk-EIC5WI6C.js +0 -1225
- package/dist/chunk-EIC5WI6C.js.map +0 -1
|
@@ -0,0 +1,1636 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ExtractedField,
|
|
3
|
+
ExtractionResult,
|
|
4
|
+
GRAPHQL_OAUTH_CONNECTION_SLOT,
|
|
5
|
+
GraphqlArgument,
|
|
6
|
+
GraphqlCredentialInput,
|
|
7
|
+
GraphqlExtractionError,
|
|
8
|
+
GraphqlIntrospectionError,
|
|
9
|
+
GraphqlInvocationError,
|
|
10
|
+
GraphqlSourceAuthInput,
|
|
11
|
+
GraphqlSourceBindingRef,
|
|
12
|
+
InvocationResult,
|
|
13
|
+
OperationBinding,
|
|
14
|
+
graphqlHeaderSlot,
|
|
15
|
+
graphqlQueryParamSlot
|
|
16
|
+
} from "./chunk-445ZPXHU.js";
|
|
17
|
+
|
|
18
|
+
// src/sdk/introspect.ts
|
|
19
|
+
import { Effect, Schema } from "effect";
|
|
20
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
21
|
+
var INTROSPECTION_QUERY = `
|
|
22
|
+
query IntrospectionQuery {
|
|
23
|
+
__schema {
|
|
24
|
+
queryType { name }
|
|
25
|
+
mutationType { name }
|
|
26
|
+
types {
|
|
27
|
+
kind
|
|
28
|
+
name
|
|
29
|
+
description
|
|
30
|
+
fields(includeDeprecated: false) {
|
|
31
|
+
name
|
|
32
|
+
description
|
|
33
|
+
args {
|
|
34
|
+
name
|
|
35
|
+
description
|
|
36
|
+
type {
|
|
37
|
+
...TypeRef
|
|
38
|
+
}
|
|
39
|
+
defaultValue
|
|
40
|
+
}
|
|
41
|
+
type {
|
|
42
|
+
...TypeRef
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
inputFields {
|
|
46
|
+
name
|
|
47
|
+
description
|
|
48
|
+
type {
|
|
49
|
+
...TypeRef
|
|
50
|
+
}
|
|
51
|
+
defaultValue
|
|
52
|
+
}
|
|
53
|
+
enumValues(includeDeprecated: false) {
|
|
54
|
+
name
|
|
55
|
+
description
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fragment TypeRef on __Type {
|
|
62
|
+
kind
|
|
63
|
+
name
|
|
64
|
+
ofType {
|
|
65
|
+
kind
|
|
66
|
+
name
|
|
67
|
+
ofType {
|
|
68
|
+
kind
|
|
69
|
+
name
|
|
70
|
+
ofType {
|
|
71
|
+
kind
|
|
72
|
+
name
|
|
73
|
+
ofType {
|
|
74
|
+
kind
|
|
75
|
+
name
|
|
76
|
+
ofType {
|
|
77
|
+
kind
|
|
78
|
+
name
|
|
79
|
+
ofType {
|
|
80
|
+
kind
|
|
81
|
+
name
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
`;
|
|
90
|
+
var IntrospectionTypeRefLeaf = Schema.Struct({
|
|
91
|
+
kind: Schema.String,
|
|
92
|
+
name: Schema.NullOr(Schema.String),
|
|
93
|
+
ofType: Schema.Null
|
|
94
|
+
});
|
|
95
|
+
var IntrospectionTypeRef5 = Schema.Struct({
|
|
96
|
+
kind: Schema.String,
|
|
97
|
+
name: Schema.NullOr(Schema.String),
|
|
98
|
+
ofType: Schema.NullOr(IntrospectionTypeRefLeaf)
|
|
99
|
+
});
|
|
100
|
+
var IntrospectionTypeRef4 = Schema.Struct({
|
|
101
|
+
kind: Schema.String,
|
|
102
|
+
name: Schema.NullOr(Schema.String),
|
|
103
|
+
ofType: Schema.NullOr(IntrospectionTypeRef5)
|
|
104
|
+
});
|
|
105
|
+
var IntrospectionTypeRef3 = Schema.Struct({
|
|
106
|
+
kind: Schema.String,
|
|
107
|
+
name: Schema.NullOr(Schema.String),
|
|
108
|
+
ofType: Schema.NullOr(IntrospectionTypeRef4)
|
|
109
|
+
});
|
|
110
|
+
var IntrospectionTypeRef2 = Schema.Struct({
|
|
111
|
+
kind: Schema.String,
|
|
112
|
+
name: Schema.NullOr(Schema.String),
|
|
113
|
+
ofType: Schema.NullOr(IntrospectionTypeRef3)
|
|
114
|
+
});
|
|
115
|
+
var IntrospectionTypeRefSchema = Schema.Struct({
|
|
116
|
+
kind: Schema.String,
|
|
117
|
+
name: Schema.NullOr(Schema.String),
|
|
118
|
+
ofType: Schema.NullOr(IntrospectionTypeRef2)
|
|
119
|
+
});
|
|
120
|
+
var IntrospectionInputValueSchema = Schema.Struct({
|
|
121
|
+
name: Schema.String,
|
|
122
|
+
description: Schema.NullOr(Schema.String),
|
|
123
|
+
type: IntrospectionTypeRefSchema,
|
|
124
|
+
defaultValue: Schema.NullOr(Schema.String)
|
|
125
|
+
});
|
|
126
|
+
var IntrospectionFieldSchema = Schema.Struct({
|
|
127
|
+
name: Schema.String,
|
|
128
|
+
description: Schema.NullOr(Schema.String),
|
|
129
|
+
args: Schema.Array(IntrospectionInputValueSchema),
|
|
130
|
+
type: IntrospectionTypeRefSchema
|
|
131
|
+
});
|
|
132
|
+
var IntrospectionTypeSchema = Schema.Struct({
|
|
133
|
+
kind: Schema.String,
|
|
134
|
+
name: Schema.String,
|
|
135
|
+
description: Schema.NullOr(Schema.String),
|
|
136
|
+
fields: Schema.NullOr(Schema.Array(IntrospectionFieldSchema)),
|
|
137
|
+
inputFields: Schema.NullOr(Schema.Array(IntrospectionInputValueSchema)),
|
|
138
|
+
enumValues: Schema.NullOr(
|
|
139
|
+
Schema.Array(
|
|
140
|
+
Schema.Struct({
|
|
141
|
+
name: Schema.String,
|
|
142
|
+
description: Schema.NullOr(Schema.String)
|
|
143
|
+
})
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
});
|
|
147
|
+
var IntrospectionResultSchema = Schema.Struct({
|
|
148
|
+
__schema: Schema.Struct({
|
|
149
|
+
queryType: Schema.NullOr(Schema.Struct({ name: Schema.String })),
|
|
150
|
+
mutationType: Schema.NullOr(Schema.Struct({ name: Schema.String })),
|
|
151
|
+
types: Schema.Array(IntrospectionTypeSchema)
|
|
152
|
+
})
|
|
153
|
+
});
|
|
154
|
+
var IntrospectionResponseSchema = Schema.Struct({
|
|
155
|
+
data: Schema.optional(IntrospectionResultSchema),
|
|
156
|
+
errors: Schema.optional(Schema.Array(Schema.Unknown))
|
|
157
|
+
});
|
|
158
|
+
var IntrospectionJsonSchema = Schema.Union([
|
|
159
|
+
Schema.Struct({ data: IntrospectionResultSchema }),
|
|
160
|
+
IntrospectionResultSchema
|
|
161
|
+
]);
|
|
162
|
+
var introspect = Effect.fn("GraphQL.introspect")(function* (endpoint, headers, queryParams) {
|
|
163
|
+
const client = yield* HttpClient.HttpClient;
|
|
164
|
+
const requestEndpoint = queryParams && Object.keys(queryParams).length > 0 ? (() => {
|
|
165
|
+
const url = new URL(endpoint);
|
|
166
|
+
for (const [name, value] of Object.entries(queryParams)) {
|
|
167
|
+
url.searchParams.set(name, value);
|
|
168
|
+
}
|
|
169
|
+
return url.toString();
|
|
170
|
+
})() : endpoint;
|
|
171
|
+
let request = HttpClientRequest.post(requestEndpoint).pipe(
|
|
172
|
+
HttpClientRequest.setHeader("Content-Type", "application/json"),
|
|
173
|
+
HttpClientRequest.bodyJsonUnsafe({
|
|
174
|
+
query: INTROSPECTION_QUERY
|
|
175
|
+
})
|
|
176
|
+
);
|
|
177
|
+
if (headers) {
|
|
178
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
179
|
+
request = HttpClientRequest.setHeader(request, k, v);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const response = yield* client.execute(request).pipe(
|
|
183
|
+
Effect.tapCause((cause) => Effect.logError("graphql introspection request failed", cause)),
|
|
184
|
+
Effect.mapError(
|
|
185
|
+
() => new GraphqlIntrospectionError({
|
|
186
|
+
message: "Failed to reach GraphQL endpoint"
|
|
187
|
+
})
|
|
188
|
+
)
|
|
189
|
+
);
|
|
190
|
+
if (response.status !== 200) {
|
|
191
|
+
return yield* new GraphqlIntrospectionError({
|
|
192
|
+
message: `Introspection failed with status ${response.status}`
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
const raw = yield* response.json.pipe(
|
|
196
|
+
Effect.tapCause((cause) => Effect.logError("graphql introspection JSON parse failed", cause)),
|
|
197
|
+
Effect.mapError(
|
|
198
|
+
() => new GraphqlIntrospectionError({
|
|
199
|
+
message: `Failed to parse introspection response as JSON`
|
|
200
|
+
})
|
|
201
|
+
)
|
|
202
|
+
);
|
|
203
|
+
const json = yield* Schema.decodeUnknownEffect(IntrospectionResponseSchema)(raw).pipe(
|
|
204
|
+
Effect.mapError(
|
|
205
|
+
() => new GraphqlIntrospectionError({
|
|
206
|
+
message: "Introspection response has an invalid shape"
|
|
207
|
+
})
|
|
208
|
+
)
|
|
209
|
+
);
|
|
210
|
+
if (json.errors && Array.isArray(json.errors) && json.errors.length > 0) {
|
|
211
|
+
return yield* new GraphqlIntrospectionError({
|
|
212
|
+
message: `Introspection returned ${json.errors.length} error(s)`
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
if (!json.data?.__schema) {
|
|
216
|
+
return yield* new GraphqlIntrospectionError({
|
|
217
|
+
message: "Introspection response missing __schema"
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
return json.data;
|
|
221
|
+
});
|
|
222
|
+
var parseIntrospectionJson = (text) => Schema.decodeUnknownEffect(Schema.fromJsonString(IntrospectionJsonSchema))(text).pipe(
|
|
223
|
+
Effect.map((parsed) => "data" in parsed ? parsed.data : parsed),
|
|
224
|
+
Effect.mapError(
|
|
225
|
+
() => new GraphqlIntrospectionError({
|
|
226
|
+
message: "Failed to parse introspection JSON"
|
|
227
|
+
})
|
|
228
|
+
)
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
// src/sdk/extract.ts
|
|
232
|
+
import { Effect as Effect2, Match, Option } from "effect";
|
|
233
|
+
var unwrapTypeName = (ref) => {
|
|
234
|
+
if (ref.name) return ref.name;
|
|
235
|
+
if (ref.ofType) return unwrapTypeName(ref.ofType);
|
|
236
|
+
return "Unknown";
|
|
237
|
+
};
|
|
238
|
+
var isNonNull = (ref) => ref.kind === "NON_NULL";
|
|
239
|
+
var buildDefinitions = (types) => {
|
|
240
|
+
const defs = {};
|
|
241
|
+
for (const [name, type] of types) {
|
|
242
|
+
if (name.startsWith("__")) continue;
|
|
243
|
+
if (type.kind === "INPUT_OBJECT" && type.inputFields) {
|
|
244
|
+
const properties = {};
|
|
245
|
+
const required = [];
|
|
246
|
+
for (const field of type.inputFields) {
|
|
247
|
+
const schema = typeRefToJsonSchema(field.type, types);
|
|
248
|
+
if (field.description) {
|
|
249
|
+
schema.description = field.description;
|
|
250
|
+
}
|
|
251
|
+
properties[field.name] = schema;
|
|
252
|
+
if (isNonNull(field.type)) {
|
|
253
|
+
required.push(field.name);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const def = { type: "object", properties };
|
|
257
|
+
if (required.length > 0) def.required = required;
|
|
258
|
+
if (type.description) def.description = type.description;
|
|
259
|
+
defs[name] = def;
|
|
260
|
+
}
|
|
261
|
+
if (type.kind === "ENUM" && type.enumValues) {
|
|
262
|
+
defs[name] = {
|
|
263
|
+
type: "string",
|
|
264
|
+
enum: type.enumValues.map((v) => v.name),
|
|
265
|
+
...type.description ? { description: type.description } : {}
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return defs;
|
|
270
|
+
};
|
|
271
|
+
var typeRefToJsonSchema = (ref, types) => Match.value(ref.kind).pipe(
|
|
272
|
+
Match.when(
|
|
273
|
+
"NON_NULL",
|
|
274
|
+
() => ref.ofType ? typeRefToJsonSchema(ref.ofType, types) : {}
|
|
275
|
+
),
|
|
276
|
+
Match.when(
|
|
277
|
+
"LIST",
|
|
278
|
+
() => ({
|
|
279
|
+
type: "array",
|
|
280
|
+
items: ref.ofType ? typeRefToJsonSchema(ref.ofType, types) : {}
|
|
281
|
+
})
|
|
282
|
+
),
|
|
283
|
+
Match.when("SCALAR", () => scalarToJsonSchema(ref.name ?? "String")),
|
|
284
|
+
Match.when(
|
|
285
|
+
"ENUM",
|
|
286
|
+
() => ref.name ? { $ref: `#/$defs/${ref.name}` } : { type: "string" }
|
|
287
|
+
),
|
|
288
|
+
Match.when(
|
|
289
|
+
"INPUT_OBJECT",
|
|
290
|
+
() => ref.name ? { $ref: `#/$defs/${ref.name}` } : { type: "object" }
|
|
291
|
+
),
|
|
292
|
+
Match.whenOr(
|
|
293
|
+
"OBJECT",
|
|
294
|
+
"INTERFACE",
|
|
295
|
+
"UNION",
|
|
296
|
+
() => ({ type: "object" })
|
|
297
|
+
),
|
|
298
|
+
Match.option,
|
|
299
|
+
Option.getOrElse(() => ({}))
|
|
300
|
+
);
|
|
301
|
+
var scalarToJsonSchema = (name) => Match.value(name).pipe(
|
|
302
|
+
Match.whenOr("String", "ID", () => ({ type: "string" })),
|
|
303
|
+
Match.when("Int", () => ({ type: "integer" })),
|
|
304
|
+
Match.when("Float", () => ({ type: "number" })),
|
|
305
|
+
Match.when("Boolean", () => ({ type: "boolean" })),
|
|
306
|
+
Match.option,
|
|
307
|
+
Option.getOrElse(
|
|
308
|
+
() => ({ type: "string", description: `Custom scalar: ${name}` })
|
|
309
|
+
)
|
|
310
|
+
);
|
|
311
|
+
var buildInputSchema = (args, types) => {
|
|
312
|
+
if (args.length === 0) return void 0;
|
|
313
|
+
const properties = {};
|
|
314
|
+
const required = [];
|
|
315
|
+
for (const arg of args) {
|
|
316
|
+
const schema = typeRefToJsonSchema(arg.type, types);
|
|
317
|
+
if (arg.description) {
|
|
318
|
+
schema.description = arg.description;
|
|
319
|
+
}
|
|
320
|
+
properties[arg.name] = schema;
|
|
321
|
+
if (isNonNull(arg.type)) {
|
|
322
|
+
required.push(arg.name);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const inputSchema = {
|
|
326
|
+
type: "object",
|
|
327
|
+
properties
|
|
328
|
+
};
|
|
329
|
+
if (required.length > 0) inputSchema.required = required;
|
|
330
|
+
return inputSchema;
|
|
331
|
+
};
|
|
332
|
+
var formatTypeRef = (ref) => Match.value(ref.kind).pipe(
|
|
333
|
+
Match.when("NON_NULL", () => ref.ofType ? `${formatTypeRef(ref.ofType)}!` : "Unknown!"),
|
|
334
|
+
Match.when("LIST", () => ref.ofType ? `[${formatTypeRef(ref.ofType)}]` : "[Unknown]"),
|
|
335
|
+
Match.option,
|
|
336
|
+
Option.getOrElse(() => ref.name ?? "Unknown")
|
|
337
|
+
);
|
|
338
|
+
var extractFields = (_schema, kind, typeName, types) => {
|
|
339
|
+
if (!typeName) return [];
|
|
340
|
+
const type = types.get(typeName);
|
|
341
|
+
if (!type?.fields) return [];
|
|
342
|
+
return type.fields.filter((f) => !f.name.startsWith("__")).map((field) => {
|
|
343
|
+
const args = field.args.map(
|
|
344
|
+
(arg) => GraphqlArgument.make({
|
|
345
|
+
name: arg.name,
|
|
346
|
+
typeName: formatTypeRef(arg.type),
|
|
347
|
+
required: isNonNull(arg.type),
|
|
348
|
+
description: arg.description ? Option.some(arg.description) : Option.none()
|
|
349
|
+
})
|
|
350
|
+
);
|
|
351
|
+
const inputSchema = buildInputSchema(field.args, types);
|
|
352
|
+
return ExtractedField.make({
|
|
353
|
+
fieldName: field.name,
|
|
354
|
+
kind,
|
|
355
|
+
description: field.description ? Option.some(field.description) : Option.none(),
|
|
356
|
+
arguments: args,
|
|
357
|
+
inputSchema: inputSchema ? Option.some(inputSchema) : Option.none(),
|
|
358
|
+
returnTypeName: unwrapTypeName(field.type)
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
};
|
|
362
|
+
var extract = (introspection) => Effect2.try({
|
|
363
|
+
try: () => {
|
|
364
|
+
const schema = introspection.__schema;
|
|
365
|
+
const typeMap = /* @__PURE__ */ new Map();
|
|
366
|
+
for (const t of schema.types) {
|
|
367
|
+
typeMap.set(t.name, t);
|
|
368
|
+
}
|
|
369
|
+
const definitions = buildDefinitions(typeMap);
|
|
370
|
+
const queryFields = extractFields(schema, "query", schema.queryType?.name, typeMap);
|
|
371
|
+
const mutationFields = extractFields(schema, "mutation", schema.mutationType?.name, typeMap);
|
|
372
|
+
return {
|
|
373
|
+
result: ExtractionResult.make({
|
|
374
|
+
schemaName: Option.none(),
|
|
375
|
+
fields: [...queryFields, ...mutationFields]
|
|
376
|
+
}),
|
|
377
|
+
definitions
|
|
378
|
+
};
|
|
379
|
+
},
|
|
380
|
+
catch: () => new GraphqlExtractionError({
|
|
381
|
+
message: "Failed to extract GraphQL schema"
|
|
382
|
+
})
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// src/sdk/invoke.ts
|
|
386
|
+
import { Effect as Effect3, Option as Option2 } from "effect";
|
|
387
|
+
import { HttpClient as HttpClient2, HttpClientRequest as HttpClientRequest2 } from "effect/unstable/http";
|
|
388
|
+
import { resolveSecretBackedMap } from "@executor-js/sdk/core";
|
|
389
|
+
var resolveHeaders = (headers, secrets) => {
|
|
390
|
+
const entries = Object.entries(headers);
|
|
391
|
+
const secretCount = entries.reduce(
|
|
392
|
+
(acc, [, value]) => typeof value === "string" ? acc : acc + 1,
|
|
393
|
+
0
|
|
394
|
+
);
|
|
395
|
+
return resolveSecretBackedMap({
|
|
396
|
+
values: headers,
|
|
397
|
+
getSecret: (secretId) => secrets.get(secretId).pipe(Effect3.catch(() => Effect3.succeed(null))),
|
|
398
|
+
missing: "drop",
|
|
399
|
+
onMissing: (name) => new GraphqlInvocationError({
|
|
400
|
+
message: `Missing secret for header "${name}"`,
|
|
401
|
+
statusCode: Option2.none()
|
|
402
|
+
})
|
|
403
|
+
}).pipe(
|
|
404
|
+
Effect3.catch(() => Effect3.succeed(void 0)),
|
|
405
|
+
Effect3.map((resolved) => resolved ?? {}),
|
|
406
|
+
Effect3.withSpan("plugin.graphql.secret.resolve", {
|
|
407
|
+
attributes: {
|
|
408
|
+
"plugin.graphql.headers.total": entries.length,
|
|
409
|
+
"plugin.graphql.headers.secret_count": secretCount
|
|
410
|
+
}
|
|
411
|
+
})
|
|
412
|
+
);
|
|
413
|
+
};
|
|
414
|
+
var endpointWithQueryParams = (endpoint, queryParams) => {
|
|
415
|
+
if (Object.keys(queryParams).length === 0) return endpoint;
|
|
416
|
+
const url = new URL(endpoint);
|
|
417
|
+
for (const [name, value] of Object.entries(queryParams)) {
|
|
418
|
+
url.searchParams.set(name, value);
|
|
419
|
+
}
|
|
420
|
+
return url.toString();
|
|
421
|
+
};
|
|
422
|
+
var endpointForTelemetry = (endpoint) => {
|
|
423
|
+
if (!URL.canParse(endpoint)) return endpoint;
|
|
424
|
+
const url = new URL(endpoint);
|
|
425
|
+
url.search = "";
|
|
426
|
+
url.hash = "";
|
|
427
|
+
return url.toString();
|
|
428
|
+
};
|
|
429
|
+
var isJsonContentType = (ct) => {
|
|
430
|
+
if (!ct) return false;
|
|
431
|
+
const normalized = ct.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
432
|
+
return normalized === "application/json" || normalized.includes("+json") || normalized.includes("json");
|
|
433
|
+
};
|
|
434
|
+
var invoke = Effect3.fn("GraphQL.invoke")(function* (operation, args, endpoint, resolvedHeaders, resolvedQueryParams = {}) {
|
|
435
|
+
const client = yield* HttpClient2.HttpClient;
|
|
436
|
+
const requestEndpoint = endpointWithQueryParams(endpoint, resolvedQueryParams);
|
|
437
|
+
const telemetryEndpoint = endpointForTelemetry(endpoint);
|
|
438
|
+
yield* Effect3.annotateCurrentSpan({
|
|
439
|
+
"http.method": "POST",
|
|
440
|
+
"http.url": telemetryEndpoint,
|
|
441
|
+
"plugin.graphql.endpoint": telemetryEndpoint,
|
|
442
|
+
"plugin.graphql.operation_kind": operation.kind,
|
|
443
|
+
"plugin.graphql.field_name": operation.fieldName,
|
|
444
|
+
"plugin.graphql.headers.resolved_count": Object.keys(resolvedHeaders).length,
|
|
445
|
+
"plugin.graphql.query_params.resolved_count": Object.keys(resolvedQueryParams).length
|
|
446
|
+
});
|
|
447
|
+
const variables = {};
|
|
448
|
+
for (const varName of operation.variableNames) {
|
|
449
|
+
if (args[varName] !== void 0) {
|
|
450
|
+
variables[varName] = args[varName];
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (typeof args.variables === "object" && args.variables !== null) {
|
|
454
|
+
Object.assign(variables, args.variables);
|
|
455
|
+
}
|
|
456
|
+
let request = HttpClientRequest2.post(requestEndpoint).pipe(
|
|
457
|
+
HttpClientRequest2.setHeader("Content-Type", "application/json"),
|
|
458
|
+
HttpClientRequest2.bodyJsonUnsafe({
|
|
459
|
+
query: operation.operationString,
|
|
460
|
+
variables: Object.keys(variables).length > 0 ? variables : void 0
|
|
461
|
+
})
|
|
462
|
+
);
|
|
463
|
+
for (const [name, value] of Object.entries(resolvedHeaders)) {
|
|
464
|
+
request = HttpClientRequest2.setHeader(request, name, value);
|
|
465
|
+
}
|
|
466
|
+
const response = yield* client.execute(request).pipe(
|
|
467
|
+
Effect3.mapError(
|
|
468
|
+
(err) => new GraphqlInvocationError({
|
|
469
|
+
message: "GraphQL request failed",
|
|
470
|
+
statusCode: Option2.none(),
|
|
471
|
+
cause: err
|
|
472
|
+
})
|
|
473
|
+
)
|
|
474
|
+
);
|
|
475
|
+
const status = response.status;
|
|
476
|
+
const contentType = response.headers["content-type"] ?? null;
|
|
477
|
+
const body = isJsonContentType(contentType) ? yield* response.json.pipe(Effect3.catch(() => response.text)) : yield* response.text;
|
|
478
|
+
const gqlBody = body;
|
|
479
|
+
const hasErrors = Array.isArray(gqlBody?.errors) && gqlBody.errors.length > 0;
|
|
480
|
+
yield* Effect3.annotateCurrentSpan({
|
|
481
|
+
"http.status_code": status,
|
|
482
|
+
"plugin.graphql.has_errors": hasErrors,
|
|
483
|
+
"plugin.graphql.error_count": hasErrors ? gqlBody.errors.length : 0
|
|
484
|
+
});
|
|
485
|
+
return InvocationResult.make({
|
|
486
|
+
status,
|
|
487
|
+
data: gqlBody?.data ?? null,
|
|
488
|
+
errors: hasErrors ? gqlBody.errors : null
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
var invokeWithLayer = (operation, args, endpoint, resolvedHeaders, resolvedQueryParams, httpClientLayer) => invoke(operation, args, endpoint, resolvedHeaders, resolvedQueryParams).pipe(
|
|
492
|
+
Effect3.provide(httpClientLayer),
|
|
493
|
+
Effect3.withSpan("plugin.graphql.invoke", {
|
|
494
|
+
attributes: {
|
|
495
|
+
"plugin.graphql.endpoint": endpointForTelemetry(endpoint),
|
|
496
|
+
"plugin.graphql.operation_kind": operation.kind,
|
|
497
|
+
"plugin.graphql.field_name": operation.fieldName
|
|
498
|
+
}
|
|
499
|
+
})
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
// src/sdk/store.ts
|
|
503
|
+
import { Effect as Effect4, Schema as Schema2 } from "effect";
|
|
504
|
+
import {
|
|
505
|
+
ConfiguredCredentialBinding,
|
|
506
|
+
defineSchema
|
|
507
|
+
} from "@executor-js/sdk/core";
|
|
508
|
+
var graphqlSchema = defineSchema({
|
|
509
|
+
graphql_source: {
|
|
510
|
+
fields: {
|
|
511
|
+
id: { type: "string", required: true },
|
|
512
|
+
scope_id: { type: "string", required: true, index: true },
|
|
513
|
+
name: { type: "string", required: true },
|
|
514
|
+
endpoint: { type: "string", required: true },
|
|
515
|
+
auth_kind: {
|
|
516
|
+
type: ["none", "oauth2"],
|
|
517
|
+
required: true,
|
|
518
|
+
defaultValue: "none"
|
|
519
|
+
},
|
|
520
|
+
auth_connection_slot: {
|
|
521
|
+
type: "string",
|
|
522
|
+
required: false
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
graphql_source_header: {
|
|
527
|
+
fields: {
|
|
528
|
+
id: { type: "string", required: true },
|
|
529
|
+
scope_id: { type: "string", required: true, index: true },
|
|
530
|
+
source_id: { type: "string", required: true, index: true },
|
|
531
|
+
name: { type: "string", required: true },
|
|
532
|
+
kind: {
|
|
533
|
+
type: ["text", "binding"],
|
|
534
|
+
required: true
|
|
535
|
+
},
|
|
536
|
+
text_value: { type: "string", required: false },
|
|
537
|
+
slot_key: { type: "string", required: false },
|
|
538
|
+
prefix: { type: "string", required: false }
|
|
539
|
+
}
|
|
540
|
+
},
|
|
541
|
+
graphql_source_query_param: {
|
|
542
|
+
fields: {
|
|
543
|
+
id: { type: "string", required: true },
|
|
544
|
+
scope_id: { type: "string", required: true, index: true },
|
|
545
|
+
source_id: { type: "string", required: true, index: true },
|
|
546
|
+
name: { type: "string", required: true },
|
|
547
|
+
kind: {
|
|
548
|
+
type: ["text", "binding"],
|
|
549
|
+
required: true
|
|
550
|
+
},
|
|
551
|
+
text_value: { type: "string", required: false },
|
|
552
|
+
slot_key: { type: "string", required: false },
|
|
553
|
+
prefix: { type: "string", required: false }
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
graphql_operation: {
|
|
557
|
+
fields: {
|
|
558
|
+
id: { type: "string", required: true },
|
|
559
|
+
scope_id: { type: "string", required: true, index: true },
|
|
560
|
+
source_id: { type: "string", required: true, index: true },
|
|
561
|
+
binding: { type: "json", required: true }
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
var OperationBindingFromJsonString = Schema2.fromJsonString(OperationBinding);
|
|
566
|
+
var decodeOperationBindingFromJsonString = Schema2.decodeUnknownSync(
|
|
567
|
+
OperationBindingFromJsonString
|
|
568
|
+
);
|
|
569
|
+
var decodeOperationBinding = Schema2.decodeUnknownSync(OperationBinding);
|
|
570
|
+
var decodeBinding = (value) => {
|
|
571
|
+
if (typeof value === "string") {
|
|
572
|
+
return decodeOperationBindingFromJsonString(value);
|
|
573
|
+
}
|
|
574
|
+
return decodeOperationBinding(value);
|
|
575
|
+
};
|
|
576
|
+
var encodeBinding = Schema2.encodeSync(OperationBinding);
|
|
577
|
+
var toJsonRecord = (value) => value;
|
|
578
|
+
var SourceRow = Schema2.Struct({
|
|
579
|
+
id: Schema2.String,
|
|
580
|
+
scope_id: Schema2.String,
|
|
581
|
+
name: Schema2.String,
|
|
582
|
+
endpoint: Schema2.String,
|
|
583
|
+
auth_kind: Schema2.Literals(["none", "oauth2"]),
|
|
584
|
+
auth_connection_slot: Schema2.NullOr(Schema2.String).pipe(Schema2.optionalKey)
|
|
585
|
+
});
|
|
586
|
+
var ChildValueRow = Schema2.Struct({
|
|
587
|
+
name: Schema2.String,
|
|
588
|
+
kind: Schema2.Literals(["text", "binding"]),
|
|
589
|
+
text_value: Schema2.NullOr(Schema2.String).pipe(Schema2.optionalKey),
|
|
590
|
+
slot_key: Schema2.NullOr(Schema2.String).pipe(Schema2.optionalKey),
|
|
591
|
+
prefix: Schema2.NullOr(Schema2.String).pipe(Schema2.optionalKey)
|
|
592
|
+
});
|
|
593
|
+
var OperationRow = Schema2.Struct({
|
|
594
|
+
id: Schema2.String,
|
|
595
|
+
source_id: Schema2.String,
|
|
596
|
+
binding: Schema2.Unknown
|
|
597
|
+
});
|
|
598
|
+
var decodeSourceRow = Schema2.decodeUnknownSync(SourceRow);
|
|
599
|
+
var decodeChildValueRow = Schema2.decodeUnknownSync(ChildValueRow);
|
|
600
|
+
var decodeOperationRow = Schema2.decodeUnknownSync(OperationRow);
|
|
601
|
+
var rowsToValueMap = (rows) => {
|
|
602
|
+
const out = {};
|
|
603
|
+
for (const rawRow of rows) {
|
|
604
|
+
const row = decodeChildValueRow(rawRow);
|
|
605
|
+
const name = row.name;
|
|
606
|
+
if (row.kind === "binding" && typeof row.slot_key === "string") {
|
|
607
|
+
out[name] = typeof row.prefix === "string" ? ConfiguredCredentialBinding.make({
|
|
608
|
+
kind: "binding",
|
|
609
|
+
slot: row.slot_key,
|
|
610
|
+
prefix: row.prefix
|
|
611
|
+
}) : ConfiguredCredentialBinding.make({
|
|
612
|
+
kind: "binding",
|
|
613
|
+
slot: row.slot_key
|
|
614
|
+
});
|
|
615
|
+
} else if (row.kind === "text" && typeof row.text_value === "string") {
|
|
616
|
+
out[name] = row.text_value;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return out;
|
|
620
|
+
};
|
|
621
|
+
var valueToChildRow = (sourceId, scope, name, value) => {
|
|
622
|
+
const id = JSON.stringify([sourceId, name]);
|
|
623
|
+
if (typeof value === "string") {
|
|
624
|
+
return {
|
|
625
|
+
id,
|
|
626
|
+
scope_id: scope,
|
|
627
|
+
source_id: sourceId,
|
|
628
|
+
name,
|
|
629
|
+
kind: "text",
|
|
630
|
+
text_value: value
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
return {
|
|
634
|
+
id,
|
|
635
|
+
scope_id: scope,
|
|
636
|
+
source_id: sourceId,
|
|
637
|
+
name,
|
|
638
|
+
kind: "binding",
|
|
639
|
+
slot_key: value.slot,
|
|
640
|
+
prefix: value.prefix
|
|
641
|
+
};
|
|
642
|
+
};
|
|
643
|
+
var rowToAuth = (row) => {
|
|
644
|
+
if (row.auth_kind === "oauth2" && typeof row.auth_connection_slot === "string") {
|
|
645
|
+
return { kind: "oauth2", connectionSlot: row.auth_connection_slot };
|
|
646
|
+
}
|
|
647
|
+
return { kind: "none" };
|
|
648
|
+
};
|
|
649
|
+
var makeDefaultGraphqlStore = ({
|
|
650
|
+
adapter: db
|
|
651
|
+
}) => {
|
|
652
|
+
const loadHeaders = (sourceId, scope) => db.findMany({
|
|
653
|
+
model: "graphql_source_header",
|
|
654
|
+
where: [
|
|
655
|
+
{ field: "source_id", value: sourceId },
|
|
656
|
+
{ field: "scope_id", value: scope }
|
|
657
|
+
]
|
|
658
|
+
}).pipe(Effect4.map(rowsToValueMap));
|
|
659
|
+
const loadQueryParams = (sourceId, scope) => db.findMany({
|
|
660
|
+
model: "graphql_source_query_param",
|
|
661
|
+
where: [
|
|
662
|
+
{ field: "source_id", value: sourceId },
|
|
663
|
+
{ field: "scope_id", value: scope }
|
|
664
|
+
]
|
|
665
|
+
}).pipe(Effect4.map(rowsToValueMap));
|
|
666
|
+
const rowToSourceWithChildren = (row) => Effect4.gen(function* () {
|
|
667
|
+
const source = decodeSourceRow(row);
|
|
668
|
+
const sourceId = source.id;
|
|
669
|
+
const scope = source.scope_id;
|
|
670
|
+
const headers = yield* loadHeaders(sourceId, scope);
|
|
671
|
+
const queryParams = yield* loadQueryParams(sourceId, scope);
|
|
672
|
+
return {
|
|
673
|
+
namespace: sourceId,
|
|
674
|
+
scope,
|
|
675
|
+
name: source.name,
|
|
676
|
+
endpoint: source.endpoint,
|
|
677
|
+
headers,
|
|
678
|
+
queryParams,
|
|
679
|
+
auth: rowToAuth(source)
|
|
680
|
+
};
|
|
681
|
+
});
|
|
682
|
+
const rowToOperation = (row) => {
|
|
683
|
+
const operation = decodeOperationRow(row);
|
|
684
|
+
return {
|
|
685
|
+
toolId: operation.id,
|
|
686
|
+
sourceId: operation.source_id,
|
|
687
|
+
binding: decodeBinding(operation.binding)
|
|
688
|
+
};
|
|
689
|
+
};
|
|
690
|
+
const replaceChildren = (model, sourceId, scope, values) => Effect4.gen(function* () {
|
|
691
|
+
yield* db.deleteMany({
|
|
692
|
+
model,
|
|
693
|
+
where: [
|
|
694
|
+
{ field: "source_id", value: sourceId },
|
|
695
|
+
{ field: "scope_id", value: scope }
|
|
696
|
+
]
|
|
697
|
+
});
|
|
698
|
+
const entries = Object.entries(values);
|
|
699
|
+
if (entries.length === 0) return;
|
|
700
|
+
yield* db.createMany({
|
|
701
|
+
model,
|
|
702
|
+
data: entries.map(([name, value]) => valueToChildRow(sourceId, scope, name, value)),
|
|
703
|
+
forceAllowId: true
|
|
704
|
+
});
|
|
705
|
+
});
|
|
706
|
+
const deleteSource = (namespace, scope) => Effect4.gen(function* () {
|
|
707
|
+
yield* db.deleteMany({
|
|
708
|
+
model: "graphql_operation",
|
|
709
|
+
where: [
|
|
710
|
+
{ field: "source_id", value: namespace },
|
|
711
|
+
{ field: "scope_id", value: scope }
|
|
712
|
+
]
|
|
713
|
+
});
|
|
714
|
+
yield* db.deleteMany({
|
|
715
|
+
model: "graphql_source_header",
|
|
716
|
+
where: [
|
|
717
|
+
{ field: "source_id", value: namespace },
|
|
718
|
+
{ field: "scope_id", value: scope }
|
|
719
|
+
]
|
|
720
|
+
});
|
|
721
|
+
yield* db.deleteMany({
|
|
722
|
+
model: "graphql_source_query_param",
|
|
723
|
+
where: [
|
|
724
|
+
{ field: "source_id", value: namespace },
|
|
725
|
+
{ field: "scope_id", value: scope }
|
|
726
|
+
]
|
|
727
|
+
});
|
|
728
|
+
yield* db.delete({
|
|
729
|
+
model: "graphql_source",
|
|
730
|
+
where: [
|
|
731
|
+
{ field: "id", value: namespace },
|
|
732
|
+
{ field: "scope_id", value: scope }
|
|
733
|
+
]
|
|
734
|
+
});
|
|
735
|
+
});
|
|
736
|
+
return {
|
|
737
|
+
upsertSource: (input, operations) => Effect4.gen(function* () {
|
|
738
|
+
yield* deleteSource(input.namespace, input.scope);
|
|
739
|
+
yield* db.create({
|
|
740
|
+
model: "graphql_source",
|
|
741
|
+
data: {
|
|
742
|
+
id: input.namespace,
|
|
743
|
+
scope_id: input.scope,
|
|
744
|
+
name: input.name,
|
|
745
|
+
endpoint: input.endpoint,
|
|
746
|
+
auth_kind: input.auth.kind,
|
|
747
|
+
auth_connection_slot: input.auth.kind === "oauth2" ? input.auth.connectionSlot : void 0
|
|
748
|
+
},
|
|
749
|
+
forceAllowId: true
|
|
750
|
+
});
|
|
751
|
+
yield* replaceChildren(
|
|
752
|
+
"graphql_source_header",
|
|
753
|
+
input.namespace,
|
|
754
|
+
input.scope,
|
|
755
|
+
input.headers
|
|
756
|
+
);
|
|
757
|
+
yield* replaceChildren(
|
|
758
|
+
"graphql_source_query_param",
|
|
759
|
+
input.namespace,
|
|
760
|
+
input.scope,
|
|
761
|
+
input.queryParams
|
|
762
|
+
);
|
|
763
|
+
if (operations.length > 0) {
|
|
764
|
+
yield* db.createMany({
|
|
765
|
+
model: "graphql_operation",
|
|
766
|
+
data: operations.map((op) => ({
|
|
767
|
+
id: op.toolId,
|
|
768
|
+
scope_id: input.scope,
|
|
769
|
+
source_id: op.sourceId,
|
|
770
|
+
binding: toJsonRecord(encodeBinding(op.binding))
|
|
771
|
+
})),
|
|
772
|
+
forceAllowId: true
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}),
|
|
776
|
+
updateSourceMeta: (namespace, scope, patch) => Effect4.gen(function* () {
|
|
777
|
+
const existing = yield* db.findOne({
|
|
778
|
+
model: "graphql_source",
|
|
779
|
+
where: [
|
|
780
|
+
{ field: "id", value: namespace },
|
|
781
|
+
{ field: "scope_id", value: scope }
|
|
782
|
+
]
|
|
783
|
+
});
|
|
784
|
+
if (!existing) return;
|
|
785
|
+
const update = {};
|
|
786
|
+
if (patch.name !== void 0) update.name = patch.name;
|
|
787
|
+
if (patch.endpoint !== void 0) update.endpoint = patch.endpoint;
|
|
788
|
+
if (patch.auth !== void 0) {
|
|
789
|
+
update.auth_kind = patch.auth.kind;
|
|
790
|
+
update.auth_connection_slot = patch.auth.kind === "oauth2" ? patch.auth.connectionSlot : null;
|
|
791
|
+
}
|
|
792
|
+
if (Object.keys(update).length > 0) {
|
|
793
|
+
yield* db.update({
|
|
794
|
+
model: "graphql_source",
|
|
795
|
+
where: [
|
|
796
|
+
{ field: "id", value: namespace },
|
|
797
|
+
{ field: "scope_id", value: scope }
|
|
798
|
+
],
|
|
799
|
+
update
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
if (patch.headers !== void 0) {
|
|
803
|
+
yield* replaceChildren("graphql_source_header", namespace, scope, patch.headers);
|
|
804
|
+
}
|
|
805
|
+
if (patch.queryParams !== void 0) {
|
|
806
|
+
yield* replaceChildren("graphql_source_query_param", namespace, scope, patch.queryParams);
|
|
807
|
+
}
|
|
808
|
+
}),
|
|
809
|
+
getSource: (namespace, scope) => Effect4.gen(function* () {
|
|
810
|
+
const row = yield* db.findOne({
|
|
811
|
+
model: "graphql_source",
|
|
812
|
+
where: [
|
|
813
|
+
{ field: "id", value: namespace },
|
|
814
|
+
{ field: "scope_id", value: scope }
|
|
815
|
+
]
|
|
816
|
+
});
|
|
817
|
+
if (!row) return null;
|
|
818
|
+
return yield* rowToSourceWithChildren(row);
|
|
819
|
+
}),
|
|
820
|
+
listSources: () => Effect4.gen(function* () {
|
|
821
|
+
const rows = yield* db.findMany({ model: "graphql_source" });
|
|
822
|
+
return yield* Effect4.forEach(rows, rowToSourceWithChildren, {
|
|
823
|
+
concurrency: "unbounded"
|
|
824
|
+
});
|
|
825
|
+
}),
|
|
826
|
+
getOperationByToolId: (toolId, scope) => db.findOne({
|
|
827
|
+
model: "graphql_operation",
|
|
828
|
+
where: [
|
|
829
|
+
{ field: "id", value: toolId },
|
|
830
|
+
{ field: "scope_id", value: scope }
|
|
831
|
+
]
|
|
832
|
+
}).pipe(Effect4.map((row) => row ? rowToOperation(row) : null)),
|
|
833
|
+
listOperationsBySource: (sourceId, scope) => db.findMany({
|
|
834
|
+
model: "graphql_operation",
|
|
835
|
+
where: [
|
|
836
|
+
{ field: "source_id", value: sourceId },
|
|
837
|
+
{ field: "scope_id", value: scope }
|
|
838
|
+
]
|
|
839
|
+
}).pipe(Effect4.map((rows) => rows.map(rowToOperation))),
|
|
840
|
+
removeSource: (namespace, scope) => deleteSource(namespace, scope)
|
|
841
|
+
};
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
// src/sdk/plugin.ts
|
|
845
|
+
import { Effect as Effect5, Match as Match2, Option as Option3, Schema as Schema3 } from "effect";
|
|
846
|
+
import {
|
|
847
|
+
ConnectionId,
|
|
848
|
+
ConfiguredCredentialBinding as ConfiguredCredentialBinding2,
|
|
849
|
+
definePlugin,
|
|
850
|
+
tool,
|
|
851
|
+
ScopeId,
|
|
852
|
+
SecretId,
|
|
853
|
+
SourceDetectionResult,
|
|
854
|
+
StorageError
|
|
855
|
+
} from "@executor-js/sdk/core";
|
|
856
|
+
import {
|
|
857
|
+
headersToConfigValues
|
|
858
|
+
} from "@executor-js/config";
|
|
859
|
+
var StaticAddSourceInputSchema = Schema3.Struct({
|
|
860
|
+
scope: Schema3.String,
|
|
861
|
+
endpoint: Schema3.String,
|
|
862
|
+
name: Schema3.optional(Schema3.String),
|
|
863
|
+
introspectionJson: Schema3.optional(Schema3.String),
|
|
864
|
+
namespace: Schema3.optional(Schema3.String),
|
|
865
|
+
headers: Schema3.optional(Schema3.Record(Schema3.String, GraphqlCredentialInput)),
|
|
866
|
+
queryParams: Schema3.optional(Schema3.Record(Schema3.String, GraphqlCredentialInput)),
|
|
867
|
+
credentialTargetScope: Schema3.optional(Schema3.String),
|
|
868
|
+
auth: Schema3.optional(GraphqlSourceAuthInput)
|
|
869
|
+
});
|
|
870
|
+
var StaticAddSourceInputStandardSchema = Schema3.toStandardSchemaV1(
|
|
871
|
+
Schema3.toStandardJSONSchemaV1(StaticAddSourceInputSchema)
|
|
872
|
+
);
|
|
873
|
+
var StaticAddSourceOutputStandardSchema = Schema3.toStandardSchemaV1(
|
|
874
|
+
Schema3.toStandardJSONSchemaV1(Schema3.Struct({ toolCount: Schema3.Number }))
|
|
875
|
+
);
|
|
876
|
+
var urlMatchesToken = (url, token) => {
|
|
877
|
+
const re = new RegExp(`(?:^|[^a-z0-9])${token}(?:$|[^a-z0-9])`, "i");
|
|
878
|
+
return re.test(url.hostname) || re.test(url.pathname);
|
|
879
|
+
};
|
|
880
|
+
var namespaceFromEndpoint = (endpoint) => {
|
|
881
|
+
try {
|
|
882
|
+
const url = new URL(endpoint);
|
|
883
|
+
return url.hostname.replace(/[^a-z0-9]+/gi, "_").toLowerCase();
|
|
884
|
+
} catch {
|
|
885
|
+
return "graphql";
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
var formatTypeRef2 = (ref) => Match2.value(ref.kind).pipe(
|
|
889
|
+
Match2.when("NON_NULL", () => ref.ofType ? `${formatTypeRef2(ref.ofType)}!` : "Unknown!"),
|
|
890
|
+
Match2.when("LIST", () => ref.ofType ? `[${formatTypeRef2(ref.ofType)}]` : "[Unknown]"),
|
|
891
|
+
Match2.option,
|
|
892
|
+
Option3.getOrElse(() => ref.name ?? "Unknown")
|
|
893
|
+
);
|
|
894
|
+
var unwrapTypeName2 = (ref) => {
|
|
895
|
+
if (ref.name) return ref.name;
|
|
896
|
+
if (ref.ofType) return unwrapTypeName2(ref.ofType);
|
|
897
|
+
return "Unknown";
|
|
898
|
+
};
|
|
899
|
+
var buildSelectionSet = (ref, types, depth, seen) => {
|
|
900
|
+
if (depth > 2) return "";
|
|
901
|
+
const leafName = unwrapTypeName2(ref);
|
|
902
|
+
if (seen.has(leafName)) return "";
|
|
903
|
+
const objectType = types.get(leafName);
|
|
904
|
+
if (!objectType?.fields) return "";
|
|
905
|
+
const kind = objectType.kind;
|
|
906
|
+
if (kind === "SCALAR" || kind === "ENUM") return "";
|
|
907
|
+
seen.add(leafName);
|
|
908
|
+
const subFields = objectType.fields.filter((f) => !f.name.startsWith("__")).slice(0, 12).map((f) => {
|
|
909
|
+
const sub = buildSelectionSet(f.type, types, depth + 1, seen);
|
|
910
|
+
return sub ? `${f.name} ${sub}` : f.name;
|
|
911
|
+
});
|
|
912
|
+
seen.delete(leafName);
|
|
913
|
+
return subFields.length > 0 ? `{ ${subFields.join(" ")} }` : "";
|
|
914
|
+
};
|
|
915
|
+
var buildOperationStringForField = (kind, field, types) => {
|
|
916
|
+
const opType = kind === "query" ? "query" : "mutation";
|
|
917
|
+
const varDefs = field.args.map((arg) => {
|
|
918
|
+
const typeName = formatTypeRef2(arg.type);
|
|
919
|
+
return `$${arg.name}: ${typeName}`;
|
|
920
|
+
});
|
|
921
|
+
const argPasses = field.args.map((arg) => `${arg.name}: $${arg.name}`);
|
|
922
|
+
const selectionSet = buildSelectionSet(field.type, types, 0, /* @__PURE__ */ new Set());
|
|
923
|
+
const varDefsStr = varDefs.length > 0 ? `(${varDefs.join(", ")})` : "";
|
|
924
|
+
const argPassStr = argPasses.length > 0 ? `(${argPasses.join(", ")})` : "";
|
|
925
|
+
return `${opType}${varDefsStr} { ${field.name}${argPassStr}${selectionSet ? ` ${selectionSet}` : ""} }`;
|
|
926
|
+
};
|
|
927
|
+
var prepareOperations = (fields, introspection) => {
|
|
928
|
+
const typeMap = /* @__PURE__ */ new Map();
|
|
929
|
+
for (const t of introspection.__schema.types) {
|
|
930
|
+
typeMap.set(t.name, t);
|
|
931
|
+
}
|
|
932
|
+
const fieldMap = /* @__PURE__ */ new Map();
|
|
933
|
+
const schema = introspection.__schema;
|
|
934
|
+
for (const rootKind of ["query", "mutation"]) {
|
|
935
|
+
const typeName = rootKind === "query" ? schema.queryType?.name : schema.mutationType?.name;
|
|
936
|
+
if (!typeName) continue;
|
|
937
|
+
const rootType = typeMap.get(typeName);
|
|
938
|
+
if (!rootType?.fields) continue;
|
|
939
|
+
for (const f of rootType.fields) {
|
|
940
|
+
if (!f.name.startsWith("__")) {
|
|
941
|
+
fieldMap.set(`${rootKind}.${f.name}`, { kind: rootKind, field: f });
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
return fields.map((extracted) => {
|
|
946
|
+
const prefix = extracted.kind === "mutation" ? "mutation" : "query";
|
|
947
|
+
const toolPath = `${prefix}.${extracted.fieldName}`;
|
|
948
|
+
const description = Option3.getOrElse(
|
|
949
|
+
extracted.description,
|
|
950
|
+
() => `GraphQL ${extracted.kind}: ${extracted.fieldName} -> ${extracted.returnTypeName}`
|
|
951
|
+
);
|
|
952
|
+
const key = `${extracted.kind}.${extracted.fieldName}`;
|
|
953
|
+
const entry = fieldMap.get(key);
|
|
954
|
+
const operationString = entry ? buildOperationStringForField(entry.kind, entry.field, typeMap) : `${extracted.kind} { ${extracted.fieldName} }`;
|
|
955
|
+
const binding = OperationBinding.make({
|
|
956
|
+
kind: extracted.kind,
|
|
957
|
+
fieldName: extracted.fieldName,
|
|
958
|
+
operationString,
|
|
959
|
+
variableNames: extracted.arguments.map((a) => a.name)
|
|
960
|
+
});
|
|
961
|
+
return {
|
|
962
|
+
toolPath,
|
|
963
|
+
description,
|
|
964
|
+
inputSchema: Option3.getOrUndefined(extracted.inputSchema),
|
|
965
|
+
binding
|
|
966
|
+
};
|
|
967
|
+
});
|
|
968
|
+
};
|
|
969
|
+
var annotationsFor = (binding) => {
|
|
970
|
+
if (binding.kind === "mutation") {
|
|
971
|
+
return {
|
|
972
|
+
requiresApproval: true,
|
|
973
|
+
approvalDescription: `mutation ${binding.fieldName}`
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
return {};
|
|
977
|
+
};
|
|
978
|
+
var toGraphqlConfigEntry = (namespace, config) => {
|
|
979
|
+
const headers = {};
|
|
980
|
+
for (const [name, value] of Object.entries(config.headers ?? {})) {
|
|
981
|
+
if (typeof value === "string" || !("kind" in value)) {
|
|
982
|
+
headers[name] = value;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
return {
|
|
986
|
+
kind: "graphql",
|
|
987
|
+
endpoint: config.endpoint,
|
|
988
|
+
introspectionJson: config.introspectionJson,
|
|
989
|
+
namespace,
|
|
990
|
+
headers: headersToConfigValues(Object.keys(headers).length > 0 ? headers : void 0)
|
|
991
|
+
};
|
|
992
|
+
};
|
|
993
|
+
var GRAPHQL_PLUGIN_ID = "graphql";
|
|
994
|
+
var scopeRanks = (ctx) => new Map(ctx.scopes.map((scope, index) => [String(scope.id), index]));
|
|
995
|
+
var scopeRank = (ranks, scopeId) => ranks.get(scopeId) ?? Infinity;
|
|
996
|
+
var coreBindingToGraphqlBinding = (binding) => GraphqlSourceBindingRef.make({
|
|
997
|
+
sourceId: binding.sourceId,
|
|
998
|
+
sourceScopeId: binding.sourceScopeId,
|
|
999
|
+
scopeId: binding.scopeId,
|
|
1000
|
+
slot: binding.slotKey,
|
|
1001
|
+
value: binding.value,
|
|
1002
|
+
createdAt: binding.createdAt,
|
|
1003
|
+
updatedAt: binding.updatedAt
|
|
1004
|
+
});
|
|
1005
|
+
var listGraphqlSourceBindings = (ctx, sourceId, sourceScope) => Effect5.gen(function* () {
|
|
1006
|
+
const ranks = scopeRanks(ctx);
|
|
1007
|
+
const sourceSourceRank = scopeRank(ranks, sourceScope);
|
|
1008
|
+
if (sourceSourceRank === Infinity) return [];
|
|
1009
|
+
const bindings = yield* ctx.credentialBindings.listForSource({
|
|
1010
|
+
pluginId: GRAPHQL_PLUGIN_ID,
|
|
1011
|
+
sourceId,
|
|
1012
|
+
sourceScope: ScopeId.make(sourceScope)
|
|
1013
|
+
});
|
|
1014
|
+
return bindings.filter((binding) => scopeRank(ranks, binding.scopeId) <= sourceSourceRank).map(coreBindingToGraphqlBinding);
|
|
1015
|
+
});
|
|
1016
|
+
var resolveGraphqlSourceBinding = (ctx, sourceId, sourceScope, slot) => Effect5.gen(function* () {
|
|
1017
|
+
const ranks = scopeRanks(ctx);
|
|
1018
|
+
const sourceSourceRank = scopeRank(ranks, sourceScope);
|
|
1019
|
+
if (sourceSourceRank === Infinity) return null;
|
|
1020
|
+
const bindings = yield* ctx.credentialBindings.listForSource({
|
|
1021
|
+
pluginId: GRAPHQL_PLUGIN_ID,
|
|
1022
|
+
sourceId,
|
|
1023
|
+
sourceScope: ScopeId.make(sourceScope)
|
|
1024
|
+
});
|
|
1025
|
+
const binding = bindings.filter(
|
|
1026
|
+
(candidate) => candidate.slotKey === slot && scopeRank(ranks, candidate.scopeId) <= sourceSourceRank
|
|
1027
|
+
).sort((a, b) => scopeRank(ranks, a.scopeId) - scopeRank(ranks, b.scopeId))[0];
|
|
1028
|
+
return binding ? coreBindingToGraphqlBinding(binding) : null;
|
|
1029
|
+
});
|
|
1030
|
+
var validateGraphqlBindingTarget = (ctx, input) => Effect5.gen(function* () {
|
|
1031
|
+
const ranks = scopeRanks(ctx);
|
|
1032
|
+
const sourceSourceRank = scopeRank(ranks, input.sourceScope);
|
|
1033
|
+
const targetRank = scopeRank(ranks, input.targetScope);
|
|
1034
|
+
const scopeList = `[${ctx.scopes.map((s) => s.id).join(", ")}]`;
|
|
1035
|
+
if (sourceSourceRank === Infinity) {
|
|
1036
|
+
return yield* new StorageError({
|
|
1037
|
+
message: `GraphQL source binding references source scope "${input.sourceScope}" which is not in the executor's scope stack ${scopeList}.`,
|
|
1038
|
+
cause: void 0
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
if (targetRank === Infinity) {
|
|
1042
|
+
return yield* new StorageError({
|
|
1043
|
+
message: `GraphQL source binding targets scope "${input.targetScope}" which is not in the executor's scope stack ${scopeList}.`,
|
|
1044
|
+
cause: void 0
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
if (targetRank > sourceSourceRank) {
|
|
1048
|
+
return yield* new StorageError({
|
|
1049
|
+
message: `GraphQL source bindings for "${input.sourceId}" cannot be written at outer scope "${input.targetScope}" because the base source lives at "${input.sourceScope}"`,
|
|
1050
|
+
cause: void 0
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
});
|
|
1054
|
+
var bindingTargetScope = (targetScope, bindings) => {
|
|
1055
|
+
if (bindings.length === 0) return Effect5.succeed(void 0);
|
|
1056
|
+
if (targetScope) return Effect5.succeed(targetScope);
|
|
1057
|
+
return Effect5.fail(
|
|
1058
|
+
new GraphqlIntrospectionError({
|
|
1059
|
+
message: "credentialTargetScope is required when adding direct GraphQL credentials"
|
|
1060
|
+
})
|
|
1061
|
+
);
|
|
1062
|
+
};
|
|
1063
|
+
var targetScopeForBinding = (fallbackTargetScope, binding) => {
|
|
1064
|
+
const targetScope = binding.targetScope ?? fallbackTargetScope;
|
|
1065
|
+
if (targetScope) return Effect5.succeed(targetScope);
|
|
1066
|
+
return Effect5.fail(
|
|
1067
|
+
new GraphqlIntrospectionError({
|
|
1068
|
+
message: "credentialTargetScope is required when adding direct GraphQL credentials"
|
|
1069
|
+
})
|
|
1070
|
+
);
|
|
1071
|
+
};
|
|
1072
|
+
var canonicalizeCredentialMap = (values, slotForName) => {
|
|
1073
|
+
const nextValues = {};
|
|
1074
|
+
const bindings = [];
|
|
1075
|
+
for (const [name, value] of Object.entries(values ?? {})) {
|
|
1076
|
+
if (typeof value === "string") {
|
|
1077
|
+
nextValues[name] = value;
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
if ("kind" in value) {
|
|
1081
|
+
nextValues[name] = value;
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
const slot = slotForName(name);
|
|
1085
|
+
nextValues[name] = ConfiguredCredentialBinding2.make({
|
|
1086
|
+
kind: "binding",
|
|
1087
|
+
slot,
|
|
1088
|
+
prefix: value.prefix
|
|
1089
|
+
});
|
|
1090
|
+
bindings.push({
|
|
1091
|
+
slot,
|
|
1092
|
+
targetScope: "targetScope" in value ? value.targetScope : void 0,
|
|
1093
|
+
value: {
|
|
1094
|
+
kind: "secret",
|
|
1095
|
+
secretId: SecretId.make(value.secretId),
|
|
1096
|
+
..."secretScopeId" in value && value.secretScopeId ? { secretScopeId: value.secretScopeId } : {}
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
return { values: nextValues, bindings };
|
|
1101
|
+
};
|
|
1102
|
+
var canonicalizeAuth = (auth) => {
|
|
1103
|
+
if (!auth || auth.kind === "none") return { auth: { kind: "none" }, bindings: [] };
|
|
1104
|
+
if ("connectionSlot" in auth) return { auth, bindings: [] };
|
|
1105
|
+
return {
|
|
1106
|
+
auth: { kind: "oauth2", connectionSlot: GRAPHQL_OAUTH_CONNECTION_SLOT },
|
|
1107
|
+
bindings: [
|
|
1108
|
+
{
|
|
1109
|
+
slot: GRAPHQL_OAUTH_CONNECTION_SLOT,
|
|
1110
|
+
value: {
|
|
1111
|
+
kind: "connection",
|
|
1112
|
+
connectionId: ConnectionId.make(auth.connectionId)
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
]
|
|
1116
|
+
};
|
|
1117
|
+
};
|
|
1118
|
+
var resolveGraphqlBindingValueMap = (ctx, values, params) => Effect5.gen(function* () {
|
|
1119
|
+
if (!values) return void 0;
|
|
1120
|
+
const resolved = {};
|
|
1121
|
+
for (const [name, value] of Object.entries(values)) {
|
|
1122
|
+
if (typeof value === "string") {
|
|
1123
|
+
resolved[name] = value;
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
const binding = yield* resolveGraphqlSourceBinding(
|
|
1127
|
+
ctx,
|
|
1128
|
+
params.sourceId,
|
|
1129
|
+
params.sourceScope,
|
|
1130
|
+
value.slot
|
|
1131
|
+
);
|
|
1132
|
+
if (binding?.value.kind === "secret") {
|
|
1133
|
+
const secret = yield* ctx.secrets.getAtScope(binding.value.secretId, binding.scopeId).pipe(
|
|
1134
|
+
Effect5.catchTag(
|
|
1135
|
+
"SecretOwnedByConnectionError",
|
|
1136
|
+
() => Effect5.fail(
|
|
1137
|
+
params.makeError(`Secret not found for ${params.missingLabel} "${name}"`)
|
|
1138
|
+
)
|
|
1139
|
+
)
|
|
1140
|
+
);
|
|
1141
|
+
if (secret === null) {
|
|
1142
|
+
return yield* Effect5.fail(
|
|
1143
|
+
params.makeError(
|
|
1144
|
+
`Missing secret "${binding.value.secretId}" for ${params.missingLabel} "${name}"`
|
|
1145
|
+
)
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
resolved[name] = value.prefix ? `${value.prefix}${secret}` : secret;
|
|
1149
|
+
continue;
|
|
1150
|
+
}
|
|
1151
|
+
if (binding?.value.kind === "text") {
|
|
1152
|
+
resolved[name] = value.prefix ? `${value.prefix}${binding.value.text}` : binding.value.text;
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
return yield* Effect5.fail(
|
|
1156
|
+
params.makeError(`Missing binding for ${params.missingLabel} "${name}"`)
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
return Object.keys(resolved).length > 0 ? resolved : void 0;
|
|
1160
|
+
});
|
|
1161
|
+
var resolveGraphqlStoredOAuthHeader = (ctx, sourceId, sourceScope, auth) => Effect5.gen(function* () {
|
|
1162
|
+
if (!auth || auth.kind === "none") return void 0;
|
|
1163
|
+
const binding = yield* resolveGraphqlSourceBinding(
|
|
1164
|
+
ctx,
|
|
1165
|
+
sourceId,
|
|
1166
|
+
sourceScope,
|
|
1167
|
+
auth.connectionSlot
|
|
1168
|
+
);
|
|
1169
|
+
if (binding?.value.kind !== "connection") {
|
|
1170
|
+
return yield* new GraphqlInvocationError({
|
|
1171
|
+
message: `Missing OAuth connection binding for GraphQL source "${sourceId}"`,
|
|
1172
|
+
statusCode: Option3.none()
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
const accessToken = yield* ctx.connections.accessTokenAtScope(
|
|
1176
|
+
binding.value.connectionId,
|
|
1177
|
+
binding.scopeId
|
|
1178
|
+
);
|
|
1179
|
+
return { Authorization: `Bearer ${accessToken}` };
|
|
1180
|
+
});
|
|
1181
|
+
var makeGraphqlExtension = (ctx, httpClientLayer, configFile) => {
|
|
1182
|
+
const resolveCredentialInputMap = (values, params) => Effect5.gen(function* () {
|
|
1183
|
+
if (!values) return void 0;
|
|
1184
|
+
const resolved = {};
|
|
1185
|
+
for (const [name, value] of Object.entries(values)) {
|
|
1186
|
+
if (typeof value === "string") {
|
|
1187
|
+
resolved[name] = value;
|
|
1188
|
+
continue;
|
|
1189
|
+
}
|
|
1190
|
+
if ("kind" in value) {
|
|
1191
|
+
const slotResolved = yield* resolveGraphqlBindingValueMap(
|
|
1192
|
+
ctx,
|
|
1193
|
+
{ [name]: value },
|
|
1194
|
+
{
|
|
1195
|
+
sourceId: params.sourceId,
|
|
1196
|
+
sourceScope: params.sourceScope,
|
|
1197
|
+
missingLabel: params.missingLabel,
|
|
1198
|
+
makeError: params.makeError
|
|
1199
|
+
}
|
|
1200
|
+
);
|
|
1201
|
+
if (slotResolved?.[name] !== void 0) resolved[name] = slotResolved[name];
|
|
1202
|
+
continue;
|
|
1203
|
+
}
|
|
1204
|
+
const secretScope = "secretScopeId" in value ? value.secretScopeId ?? value.targetScope : params.targetScope ?? params.sourceScope;
|
|
1205
|
+
const secret = yield* ctx.secrets.getAtScope(SecretId.make(value.secretId), secretScope).pipe(
|
|
1206
|
+
Effect5.catchTag(
|
|
1207
|
+
"SecretOwnedByConnectionError",
|
|
1208
|
+
() => Effect5.fail(
|
|
1209
|
+
params.makeError(`Secret not found for ${params.missingLabel} "${name}"`)
|
|
1210
|
+
)
|
|
1211
|
+
)
|
|
1212
|
+
);
|
|
1213
|
+
if (secret === null) {
|
|
1214
|
+
return yield* Effect5.fail(
|
|
1215
|
+
params.makeError(
|
|
1216
|
+
`Missing secret "${value.secretId}" for ${params.missingLabel} "${name}"`
|
|
1217
|
+
)
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
resolved[name] = value.prefix ? `${value.prefix}${secret}` : secret;
|
|
1221
|
+
}
|
|
1222
|
+
return Object.keys(resolved).length > 0 ? resolved : void 0;
|
|
1223
|
+
});
|
|
1224
|
+
const resolveOAuthInputHeader = (sourceId, sourceScope, targetScope, auth) => Effect5.gen(function* () {
|
|
1225
|
+
if (!auth || auth.kind === "none") return void 0;
|
|
1226
|
+
const connection = "connectionId" in auth ? { id: auth.connectionId, scope: targetScope ?? sourceScope } : yield* Effect5.gen(function* () {
|
|
1227
|
+
const binding = yield* resolveGraphqlSourceBinding(
|
|
1228
|
+
ctx,
|
|
1229
|
+
sourceId,
|
|
1230
|
+
sourceScope,
|
|
1231
|
+
auth.connectionSlot
|
|
1232
|
+
);
|
|
1233
|
+
return binding?.value.kind === "connection" ? { id: binding.value.connectionId, scope: binding.scopeId } : null;
|
|
1234
|
+
});
|
|
1235
|
+
if (connection === null) {
|
|
1236
|
+
return yield* new GraphqlIntrospectionError({
|
|
1237
|
+
message: `Missing OAuth connection binding for "${sourceId}"`
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
const accessToken = yield* ctx.connections.accessTokenAtScope(connection.id, connection.scope).pipe(
|
|
1241
|
+
Effect5.mapError(
|
|
1242
|
+
() => new GraphqlIntrospectionError({
|
|
1243
|
+
message: `Failed to resolve OAuth connection "${connection.id}"`
|
|
1244
|
+
})
|
|
1245
|
+
)
|
|
1246
|
+
);
|
|
1247
|
+
return { Authorization: `Bearer ${accessToken}` };
|
|
1248
|
+
});
|
|
1249
|
+
const addSourceInternal = (config) => ctx.transaction(
|
|
1250
|
+
Effect5.gen(function* () {
|
|
1251
|
+
const namespace = config.namespace ?? namespaceFromEndpoint(config.endpoint);
|
|
1252
|
+
const canonicalHeaders = canonicalizeCredentialMap(config.headers, graphqlHeaderSlot);
|
|
1253
|
+
const canonicalQueryParams = canonicalizeCredentialMap(
|
|
1254
|
+
config.queryParams,
|
|
1255
|
+
graphqlQueryParamSlot
|
|
1256
|
+
);
|
|
1257
|
+
const canonicalAuth = canonicalizeAuth(config.auth);
|
|
1258
|
+
const directBindings = [
|
|
1259
|
+
...canonicalHeaders.bindings,
|
|
1260
|
+
...canonicalQueryParams.bindings,
|
|
1261
|
+
...canonicalAuth.bindings
|
|
1262
|
+
];
|
|
1263
|
+
for (const binding of directBindings) {
|
|
1264
|
+
const bindingTargetScope2 = yield* targetScopeForBinding(
|
|
1265
|
+
config.credentialTargetScope,
|
|
1266
|
+
binding
|
|
1267
|
+
);
|
|
1268
|
+
yield* validateGraphqlBindingTarget(ctx, {
|
|
1269
|
+
sourceId: namespace,
|
|
1270
|
+
sourceScope: config.scope,
|
|
1271
|
+
targetScope: bindingTargetScope2
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
const targetScope = directBindings[0] !== void 0 ? yield* targetScopeForBinding(config.credentialTargetScope, directBindings[0]) : void 0;
|
|
1275
|
+
let introspectionResult;
|
|
1276
|
+
if (config.introspectionJson) {
|
|
1277
|
+
introspectionResult = yield* parseIntrospectionJson(config.introspectionJson);
|
|
1278
|
+
} else {
|
|
1279
|
+
const resolvedHeaders = yield* resolveCredentialInputMap(config.headers, {
|
|
1280
|
+
sourceId: namespace,
|
|
1281
|
+
sourceScope: config.scope,
|
|
1282
|
+
targetScope,
|
|
1283
|
+
missingLabel: "header",
|
|
1284
|
+
makeError: (message) => new GraphqlIntrospectionError({ message })
|
|
1285
|
+
});
|
|
1286
|
+
const oauthHeader = yield* resolveOAuthInputHeader(
|
|
1287
|
+
namespace,
|
|
1288
|
+
config.scope,
|
|
1289
|
+
targetScope,
|
|
1290
|
+
config.auth
|
|
1291
|
+
);
|
|
1292
|
+
const resolvedQueryParams = yield* resolveCredentialInputMap(config.queryParams, {
|
|
1293
|
+
sourceId: namespace,
|
|
1294
|
+
sourceScope: config.scope,
|
|
1295
|
+
targetScope,
|
|
1296
|
+
missingLabel: "query parameter",
|
|
1297
|
+
makeError: (message) => new GraphqlIntrospectionError({ message })
|
|
1298
|
+
});
|
|
1299
|
+
introspectionResult = yield* introspect(
|
|
1300
|
+
config.endpoint,
|
|
1301
|
+
{ ...resolvedHeaders ?? {}, ...oauthHeader ?? {} },
|
|
1302
|
+
resolvedQueryParams
|
|
1303
|
+
).pipe(Effect5.provide(httpClientLayer));
|
|
1304
|
+
}
|
|
1305
|
+
const { result, definitions } = yield* extract(introspectionResult);
|
|
1306
|
+
const prepared = prepareOperations(result.fields, introspectionResult);
|
|
1307
|
+
const displayName = config.name?.trim() || namespace;
|
|
1308
|
+
const storedSource = {
|
|
1309
|
+
namespace,
|
|
1310
|
+
scope: config.scope,
|
|
1311
|
+
name: displayName,
|
|
1312
|
+
endpoint: config.endpoint,
|
|
1313
|
+
headers: canonicalHeaders.values,
|
|
1314
|
+
queryParams: canonicalQueryParams.values,
|
|
1315
|
+
auth: canonicalAuth.auth
|
|
1316
|
+
};
|
|
1317
|
+
const storedOps = prepared.map((p) => ({
|
|
1318
|
+
toolId: `${namespace}.${p.toolPath}`,
|
|
1319
|
+
sourceId: namespace,
|
|
1320
|
+
binding: p.binding
|
|
1321
|
+
}));
|
|
1322
|
+
yield* ctx.storage.upsertSource(storedSource, storedOps);
|
|
1323
|
+
yield* ctx.core.sources.register({
|
|
1324
|
+
id: namespace,
|
|
1325
|
+
scope: config.scope,
|
|
1326
|
+
kind: "graphql",
|
|
1327
|
+
name: displayName,
|
|
1328
|
+
url: config.endpoint,
|
|
1329
|
+
canRemove: true,
|
|
1330
|
+
canRefresh: false,
|
|
1331
|
+
canEdit: true,
|
|
1332
|
+
tools: prepared.map((p) => ({
|
|
1333
|
+
name: p.toolPath,
|
|
1334
|
+
description: p.description,
|
|
1335
|
+
inputSchema: p.inputSchema
|
|
1336
|
+
}))
|
|
1337
|
+
});
|
|
1338
|
+
if (Object.keys(definitions).length > 0) {
|
|
1339
|
+
yield* ctx.core.definitions.register({
|
|
1340
|
+
sourceId: namespace,
|
|
1341
|
+
scope: config.scope,
|
|
1342
|
+
definitions
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
if (directBindings.length > 0) {
|
|
1346
|
+
for (const binding of directBindings) {
|
|
1347
|
+
const bindingTargetScope2 = yield* targetScopeForBinding(
|
|
1348
|
+
config.credentialTargetScope,
|
|
1349
|
+
binding
|
|
1350
|
+
);
|
|
1351
|
+
yield* ctx.credentialBindings.set({
|
|
1352
|
+
targetScope: ScopeId.make(bindingTargetScope2),
|
|
1353
|
+
pluginId: GRAPHQL_PLUGIN_ID,
|
|
1354
|
+
sourceId: namespace,
|
|
1355
|
+
sourceScope: ScopeId.make(config.scope),
|
|
1356
|
+
slotKey: binding.slot,
|
|
1357
|
+
value: binding.value
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
return { toolCount: prepared.length, namespace };
|
|
1362
|
+
})
|
|
1363
|
+
);
|
|
1364
|
+
return {
|
|
1365
|
+
addSource: (config) => addSourceInternal(config).pipe(
|
|
1366
|
+
Effect5.tap(
|
|
1367
|
+
(result) => configFile ? configFile.upsertSource(toGraphqlConfigEntry(result.namespace, config)) : Effect5.void
|
|
1368
|
+
)
|
|
1369
|
+
),
|
|
1370
|
+
removeSource: (namespace, scope) => Effect5.gen(function* () {
|
|
1371
|
+
yield* ctx.transaction(
|
|
1372
|
+
Effect5.gen(function* () {
|
|
1373
|
+
yield* ctx.credentialBindings.removeForSource({
|
|
1374
|
+
pluginId: GRAPHQL_PLUGIN_ID,
|
|
1375
|
+
sourceId: namespace,
|
|
1376
|
+
sourceScope: ScopeId.make(scope)
|
|
1377
|
+
});
|
|
1378
|
+
yield* ctx.storage.removeSource(namespace, scope);
|
|
1379
|
+
yield* ctx.core.sources.unregister({ id: namespace, targetScope: scope });
|
|
1380
|
+
})
|
|
1381
|
+
);
|
|
1382
|
+
if (configFile) {
|
|
1383
|
+
yield* configFile.removeSource(namespace);
|
|
1384
|
+
}
|
|
1385
|
+
}),
|
|
1386
|
+
getSource: (namespace, scope) => ctx.storage.getSource(namespace, scope),
|
|
1387
|
+
updateSource: (namespace, scope, input) => Effect5.gen(function* () {
|
|
1388
|
+
const existing = yield* ctx.storage.getSource(namespace, scope);
|
|
1389
|
+
if (!existing) return;
|
|
1390
|
+
const canonicalHeaders = input.headers !== void 0 ? canonicalizeCredentialMap(input.headers, graphqlHeaderSlot) : null;
|
|
1391
|
+
const canonicalQueryParams = input.queryParams !== void 0 ? canonicalizeCredentialMap(input.queryParams, graphqlQueryParamSlot) : null;
|
|
1392
|
+
const canonicalAuth = input.auth !== void 0 ? canonicalizeAuth(input.auth) : null;
|
|
1393
|
+
const directBindings = [
|
|
1394
|
+
...canonicalHeaders?.bindings ?? [],
|
|
1395
|
+
...canonicalQueryParams?.bindings ?? [],
|
|
1396
|
+
...canonicalAuth?.bindings ?? []
|
|
1397
|
+
];
|
|
1398
|
+
const targetScope = yield* bindingTargetScope(input.credentialTargetScope, directBindings);
|
|
1399
|
+
if (targetScope) {
|
|
1400
|
+
yield* validateGraphqlBindingTarget(ctx, {
|
|
1401
|
+
sourceId: namespace,
|
|
1402
|
+
sourceScope: scope,
|
|
1403
|
+
targetScope
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
const affectedPrefixes = [
|
|
1407
|
+
...input.headers !== void 0 ? ["header:"] : [],
|
|
1408
|
+
...input.queryParams !== void 0 ? ["query_param:"] : [],
|
|
1409
|
+
...input.auth !== void 0 ? ["auth:"] : []
|
|
1410
|
+
];
|
|
1411
|
+
const replacementTargetScope = targetScope ?? input.credentialTargetScope ?? scope;
|
|
1412
|
+
yield* ctx.transaction(
|
|
1413
|
+
Effect5.gen(function* () {
|
|
1414
|
+
yield* ctx.storage.updateSourceMeta(namespace, scope, {
|
|
1415
|
+
name: input.name?.trim() || void 0,
|
|
1416
|
+
endpoint: input.endpoint,
|
|
1417
|
+
headers: canonicalHeaders?.values,
|
|
1418
|
+
queryParams: canonicalQueryParams?.values,
|
|
1419
|
+
auth: canonicalAuth?.auth
|
|
1420
|
+
});
|
|
1421
|
+
if (affectedPrefixes.length > 0 || directBindings.length > 0) {
|
|
1422
|
+
yield* ctx.credentialBindings.replaceForSource({
|
|
1423
|
+
targetScope: ScopeId.make(replacementTargetScope),
|
|
1424
|
+
pluginId: GRAPHQL_PLUGIN_ID,
|
|
1425
|
+
sourceId: namespace,
|
|
1426
|
+
sourceScope: ScopeId.make(scope),
|
|
1427
|
+
slotPrefixes: affectedPrefixes,
|
|
1428
|
+
bindings: directBindings.map((binding) => ({
|
|
1429
|
+
slotKey: binding.slot,
|
|
1430
|
+
value: binding.value
|
|
1431
|
+
}))
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
})
|
|
1435
|
+
);
|
|
1436
|
+
}),
|
|
1437
|
+
listSourceBindings: (sourceId, sourceScope) => listGraphqlSourceBindings(ctx, sourceId, sourceScope),
|
|
1438
|
+
setSourceBinding: (input) => Effect5.gen(function* () {
|
|
1439
|
+
yield* validateGraphqlBindingTarget(ctx, {
|
|
1440
|
+
sourceId: input.sourceId,
|
|
1441
|
+
sourceScope: input.sourceScope,
|
|
1442
|
+
targetScope: input.scope
|
|
1443
|
+
});
|
|
1444
|
+
const binding = yield* ctx.credentialBindings.set({
|
|
1445
|
+
targetScope: input.scope,
|
|
1446
|
+
pluginId: GRAPHQL_PLUGIN_ID,
|
|
1447
|
+
sourceId: input.sourceId,
|
|
1448
|
+
sourceScope: input.sourceScope,
|
|
1449
|
+
slotKey: input.slot,
|
|
1450
|
+
value: input.value
|
|
1451
|
+
});
|
|
1452
|
+
return coreBindingToGraphqlBinding(binding);
|
|
1453
|
+
}),
|
|
1454
|
+
removeSourceBinding: (sourceId, sourceScope, slot, scope) => Effect5.gen(function* () {
|
|
1455
|
+
yield* validateGraphqlBindingTarget(ctx, {
|
|
1456
|
+
sourceId,
|
|
1457
|
+
sourceScope,
|
|
1458
|
+
targetScope: scope
|
|
1459
|
+
});
|
|
1460
|
+
yield* ctx.credentialBindings.remove({
|
|
1461
|
+
targetScope: ScopeId.make(scope),
|
|
1462
|
+
pluginId: GRAPHQL_PLUGIN_ID,
|
|
1463
|
+
sourceId,
|
|
1464
|
+
sourceScope: ScopeId.make(sourceScope),
|
|
1465
|
+
slotKey: slot
|
|
1466
|
+
});
|
|
1467
|
+
})
|
|
1468
|
+
};
|
|
1469
|
+
};
|
|
1470
|
+
var graphqlPlugin = definePlugin((options) => {
|
|
1471
|
+
return {
|
|
1472
|
+
id: "graphql",
|
|
1473
|
+
packageName: "@executor-js/plugin-graphql",
|
|
1474
|
+
schema: graphqlSchema,
|
|
1475
|
+
storage: (deps) => makeDefaultGraphqlStore(deps),
|
|
1476
|
+
extension: (ctx) => makeGraphqlExtension(
|
|
1477
|
+
ctx,
|
|
1478
|
+
options?.httpClientLayer ?? ctx.httpClientLayer,
|
|
1479
|
+
options?.configFile
|
|
1480
|
+
),
|
|
1481
|
+
staticSources: (self) => [
|
|
1482
|
+
{
|
|
1483
|
+
id: "graphql",
|
|
1484
|
+
kind: "executor",
|
|
1485
|
+
name: "GraphQL",
|
|
1486
|
+
tools: [
|
|
1487
|
+
tool({
|
|
1488
|
+
name: "addSource",
|
|
1489
|
+
description: "Add a GraphQL endpoint and register its operations as tools",
|
|
1490
|
+
annotations: {
|
|
1491
|
+
requiresApproval: true,
|
|
1492
|
+
approvalDescription: "Add a GraphQL source"
|
|
1493
|
+
},
|
|
1494
|
+
inputSchema: StaticAddSourceInputStandardSchema,
|
|
1495
|
+
outputSchema: StaticAddSourceOutputStandardSchema,
|
|
1496
|
+
execute: (input) => self.addSource(input)
|
|
1497
|
+
})
|
|
1498
|
+
]
|
|
1499
|
+
}
|
|
1500
|
+
],
|
|
1501
|
+
invokeTool: ({ ctx, toolRow, args }) => Effect5.gen(function* () {
|
|
1502
|
+
const httpClientLayer = options?.httpClientLayer ?? ctx.httpClientLayer;
|
|
1503
|
+
const toolScope = toolRow.scope_id;
|
|
1504
|
+
const op = yield* ctx.storage.getOperationByToolId(toolRow.id, toolScope);
|
|
1505
|
+
if (!op) {
|
|
1506
|
+
return yield* new GraphqlInvocationError({
|
|
1507
|
+
message: `No GraphQL operation found for tool "${toolRow.id}"`,
|
|
1508
|
+
statusCode: Option3.none()
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
const source = yield* ctx.storage.getSource(op.sourceId, toolScope);
|
|
1512
|
+
if (!source) {
|
|
1513
|
+
return yield* new GraphqlInvocationError({
|
|
1514
|
+
message: `No GraphQL source found for "${op.sourceId}"`,
|
|
1515
|
+
statusCode: Option3.none()
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
const resolvedHeaders = (yield* resolveGraphqlBindingValueMap(ctx, source.headers, {
|
|
1519
|
+
sourceId: source.namespace,
|
|
1520
|
+
sourceScope: source.scope,
|
|
1521
|
+
missingLabel: "header",
|
|
1522
|
+
makeError: (message) => new GraphqlInvocationError({ message, statusCode: Option3.none() })
|
|
1523
|
+
})) ?? {};
|
|
1524
|
+
const resolvedQueryParams = (yield* resolveGraphqlBindingValueMap(ctx, source.queryParams, {
|
|
1525
|
+
sourceId: source.namespace,
|
|
1526
|
+
sourceScope: source.scope,
|
|
1527
|
+
missingLabel: "query parameter",
|
|
1528
|
+
makeError: (message) => new GraphqlInvocationError({ message, statusCode: Option3.none() })
|
|
1529
|
+
})) ?? {};
|
|
1530
|
+
const oauthHeader = yield* resolveGraphqlStoredOAuthHeader(
|
|
1531
|
+
ctx,
|
|
1532
|
+
source.namespace,
|
|
1533
|
+
source.scope,
|
|
1534
|
+
source.auth
|
|
1535
|
+
);
|
|
1536
|
+
Object.assign(resolvedHeaders, oauthHeader ?? {});
|
|
1537
|
+
const result = yield* invokeWithLayer(
|
|
1538
|
+
op.binding,
|
|
1539
|
+
args ?? {},
|
|
1540
|
+
source.endpoint,
|
|
1541
|
+
resolvedHeaders,
|
|
1542
|
+
resolvedQueryParams,
|
|
1543
|
+
httpClientLayer
|
|
1544
|
+
);
|
|
1545
|
+
return result;
|
|
1546
|
+
}),
|
|
1547
|
+
resolveAnnotations: ({ ctx, sourceId, toolRows }) => Effect5.gen(function* () {
|
|
1548
|
+
const scopes = /* @__PURE__ */ new Set();
|
|
1549
|
+
for (const row of toolRows) {
|
|
1550
|
+
scopes.add(row.scope_id);
|
|
1551
|
+
}
|
|
1552
|
+
const entries = yield* Effect5.forEach(
|
|
1553
|
+
[...scopes],
|
|
1554
|
+
(scope) => Effect5.gen(function* () {
|
|
1555
|
+
const ops = yield* ctx.storage.listOperationsBySource(sourceId, scope);
|
|
1556
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1557
|
+
for (const op of ops) byId.set(op.toolId, op.binding);
|
|
1558
|
+
return [scope, byId];
|
|
1559
|
+
}),
|
|
1560
|
+
{ concurrency: "unbounded" }
|
|
1561
|
+
);
|
|
1562
|
+
const byScope = new Map(entries);
|
|
1563
|
+
const out = {};
|
|
1564
|
+
for (const row of toolRows) {
|
|
1565
|
+
const binding = byScope.get(row.scope_id)?.get(row.id);
|
|
1566
|
+
if (binding) out[row.id] = annotationsFor(binding);
|
|
1567
|
+
}
|
|
1568
|
+
return out;
|
|
1569
|
+
}),
|
|
1570
|
+
removeSource: ({ ctx, sourceId, scope }) => Effect5.gen(function* () {
|
|
1571
|
+
yield* ctx.transaction(
|
|
1572
|
+
Effect5.gen(function* () {
|
|
1573
|
+
yield* ctx.credentialBindings.removeForSource({
|
|
1574
|
+
pluginId: GRAPHQL_PLUGIN_ID,
|
|
1575
|
+
sourceId,
|
|
1576
|
+
sourceScope: ScopeId.make(scope)
|
|
1577
|
+
});
|
|
1578
|
+
yield* ctx.storage.removeSource(sourceId, scope);
|
|
1579
|
+
})
|
|
1580
|
+
);
|
|
1581
|
+
if (options?.configFile) {
|
|
1582
|
+
yield* options.configFile.removeSource(sourceId);
|
|
1583
|
+
}
|
|
1584
|
+
}),
|
|
1585
|
+
usagesForSecret: () => Effect5.succeed([]),
|
|
1586
|
+
usagesForConnection: () => Effect5.succeed([]),
|
|
1587
|
+
detect: ({ ctx, url }) => Effect5.gen(function* () {
|
|
1588
|
+
const httpClientLayer = options?.httpClientLayer ?? ctx.httpClientLayer;
|
|
1589
|
+
const trimmed = url.trim();
|
|
1590
|
+
if (!trimmed) return null;
|
|
1591
|
+
const parsed = yield* Effect5.try({
|
|
1592
|
+
try: () => new URL(trimmed),
|
|
1593
|
+
catch: (cause) => cause
|
|
1594
|
+
}).pipe(Effect5.option);
|
|
1595
|
+
if (Option3.isNone(parsed)) return null;
|
|
1596
|
+
const ok = yield* introspect(trimmed).pipe(
|
|
1597
|
+
Effect5.provide(httpClientLayer),
|
|
1598
|
+
Effect5.map(() => true),
|
|
1599
|
+
Effect5.catch(() => Effect5.succeed(false))
|
|
1600
|
+
);
|
|
1601
|
+
const name = namespaceFromEndpoint(trimmed);
|
|
1602
|
+
if (ok) {
|
|
1603
|
+
return SourceDetectionResult.make({
|
|
1604
|
+
kind: "graphql",
|
|
1605
|
+
confidence: "high",
|
|
1606
|
+
endpoint: trimmed,
|
|
1607
|
+
name,
|
|
1608
|
+
namespace: name
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
if (urlMatchesToken(parsed.value, "graphql")) {
|
|
1612
|
+
return SourceDetectionResult.make({
|
|
1613
|
+
kind: "graphql",
|
|
1614
|
+
confidence: "low",
|
|
1615
|
+
endpoint: trimmed,
|
|
1616
|
+
name,
|
|
1617
|
+
namespace: name
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
return null;
|
|
1621
|
+
})
|
|
1622
|
+
};
|
|
1623
|
+
});
|
|
1624
|
+
|
|
1625
|
+
export {
|
|
1626
|
+
introspect,
|
|
1627
|
+
parseIntrospectionJson,
|
|
1628
|
+
extract,
|
|
1629
|
+
resolveHeaders,
|
|
1630
|
+
invoke,
|
|
1631
|
+
invokeWithLayer,
|
|
1632
|
+
graphqlSchema,
|
|
1633
|
+
makeDefaultGraphqlStore,
|
|
1634
|
+
graphqlPlugin
|
|
1635
|
+
};
|
|
1636
|
+
//# sourceMappingURL=chunk-PO2TPM5B.js.map
|