@executor-js/plugin-graphql 0.1.0 → 0.2.1

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.
Files changed (37) hide show
  1. package/dist/AddGraphqlSource-LGXJQEAR.js +239 -0
  2. package/dist/AddGraphqlSource-LGXJQEAR.js.map +1 -0
  3. package/dist/EditGraphqlSource-5Y47ZAZ7.js +251 -0
  4. package/dist/EditGraphqlSource-5Y47ZAZ7.js.map +1 -0
  5. package/dist/GraphqlSourceSummary-F3JWR4YN.js +70 -0
  6. package/dist/GraphqlSourceSummary-F3JWR4YN.js.map +1 -0
  7. package/dist/api/group.d.ts +169 -13
  8. package/dist/api/handlers.d.ts +15 -3
  9. package/dist/api/index.d.ts +410 -0
  10. package/dist/chunk-7QSGNR4C.js +162 -0
  11. package/dist/chunk-7QSGNR4C.js.map +1 -0
  12. package/dist/chunk-HDPYOBBG.js +1633 -0
  13. package/dist/chunk-HDPYOBBG.js.map +1 -0
  14. package/dist/chunk-M4SJY6CB.js +45 -0
  15. package/dist/chunk-M4SJY6CB.js.map +1 -0
  16. package/dist/chunk-WPRU5C6M.js +182 -0
  17. package/dist/chunk-WPRU5C6M.js.map +1 -0
  18. package/dist/client.js +75 -0
  19. package/dist/client.js.map +1 -0
  20. package/dist/core.js +32 -10
  21. package/dist/index.js +2 -1
  22. package/dist/react/GraphqlSourceFields.d.ts +8 -0
  23. package/dist/react/GraphqlSourceSummary.d.ts +3 -1
  24. package/dist/react/atoms.d.ts +170 -10
  25. package/dist/react/client.d.ts +164 -12
  26. package/dist/sdk/index.d.ts +1 -1
  27. package/dist/sdk/introspect.d.ts +437 -43
  28. package/dist/sdk/invoke.d.ts +3 -2
  29. package/dist/sdk/plugin.d.ts +120 -170
  30. package/dist/sdk/store.d.ts +84 -11
  31. package/dist/sdk/types.d.ts +107 -3
  32. package/dist/testing/index.d.ts +52 -0
  33. package/dist/testing.js +131 -0
  34. package/dist/testing.js.map +1 -0
  35. package/package.json +18 -4
  36. package/dist/chunk-EIC5WI6C.js +0 -1225
  37. package/dist/chunk-EIC5WI6C.js.map +0 -1
@@ -0,0 +1,1633 @@
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-7QSGNR4C.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) => new GraphqlArgument({
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 new ExtractedField({
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: new ExtractionResult({
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 new InvocationResult({
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" ? new ConfiguredCredentialBinding({
608
+ kind: "binding",
609
+ slot: row.slot_key,
610
+ prefix: row.prefix
611
+ }) : new ConfiguredCredentialBinding({
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 = new OperationBinding({
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) => new GraphqlSourceBindingRef({
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] = new ConfiguredCredentialBinding2({
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
+ }),
1582
+ usagesForSecret: () => Effect5.succeed([]),
1583
+ usagesForConnection: () => Effect5.succeed([]),
1584
+ detect: ({ ctx, url }) => Effect5.gen(function* () {
1585
+ const httpClientLayer = options?.httpClientLayer ?? ctx.httpClientLayer;
1586
+ const trimmed = url.trim();
1587
+ if (!trimmed) return null;
1588
+ const parsed = yield* Effect5.try({
1589
+ try: () => new URL(trimmed),
1590
+ catch: (cause) => cause
1591
+ }).pipe(Effect5.option);
1592
+ if (Option3.isNone(parsed)) return null;
1593
+ const ok = yield* introspect(trimmed).pipe(
1594
+ Effect5.provide(httpClientLayer),
1595
+ Effect5.map(() => true),
1596
+ Effect5.catch(() => Effect5.succeed(false))
1597
+ );
1598
+ const name = namespaceFromEndpoint(trimmed);
1599
+ if (ok) {
1600
+ return new SourceDetectionResult({
1601
+ kind: "graphql",
1602
+ confidence: "high",
1603
+ endpoint: trimmed,
1604
+ name,
1605
+ namespace: name
1606
+ });
1607
+ }
1608
+ if (urlMatchesToken(parsed.value, "graphql")) {
1609
+ return new SourceDetectionResult({
1610
+ kind: "graphql",
1611
+ confidence: "low",
1612
+ endpoint: trimmed,
1613
+ name,
1614
+ namespace: name
1615
+ });
1616
+ }
1617
+ return null;
1618
+ })
1619
+ };
1620
+ });
1621
+
1622
+ export {
1623
+ introspect,
1624
+ parseIntrospectionJson,
1625
+ extract,
1626
+ resolveHeaders,
1627
+ invoke,
1628
+ invokeWithLayer,
1629
+ graphqlSchema,
1630
+ makeDefaultGraphqlStore,
1631
+ graphqlPlugin
1632
+ };
1633
+ //# sourceMappingURL=chunk-HDPYOBBG.js.map