| @@ -1,4 +1,4 @@ |
| 1 | import { Effect, Option } from "effect"; | 1 | import { Effect, Option, Predicate, Schema } from "effect"; |
| 2 | | 2 | |
| 3 | import { | 3 | import { |
| 4 | ScopeId, | 4 | ScopeId, |
| @@ -12,16 +12,12 @@ import { · 7 unmodified lines |
| 12 | } from "@executor-js/sdk/core"; | 12 | } from "@executor-js/sdk/core"; |
| 13 | | 13 | |
| 14 | import { GoogleDiscoveryGroup } from "../api/group"; | 14 | import { GoogleDiscoveryGroup } from "../api/group"; |
| 15 | import { | 15 | import { GoogleDiscoveryExtensionService, GoogleDiscoveryHandlers } from "../api/handlers"; |
| 16 | GoogleDiscoveryExtensionService, | | |
| 17 | GoogleDiscoveryHandlers, | | |
| 18 | } from "../api/handlers"; | | |
| 19 | | 16 | |
| 20 | import { | 17 | import { |
| 21 | googleDiscoverySchema, | 18 | googleDiscoverySchema, |
| 22 | makeGoogleDiscoveryStore, | 19 | makeGoogleDiscoveryStore, |
| 23 | type GoogleDiscoveryStore, | 20 | type GoogleDiscoveryStore, |
| 24 | type GoogleDiscoveryStoredSource, | | |
| 25 | } from "./binding-store"; | 21 | } from "./binding-store"; |
| 26 | import { extractGoogleDiscoveryManifest } from "./document"; | 22 | import { extractGoogleDiscoveryManifest } from "./document"; |
| 27 | import { annotationsForOperation, invokeGoogleDiscoveryTool } from "./invoke"; | 23 | import { annotationsForOperation, invokeGoogleDiscoveryTool } from "./invoke"; |
| @@ -86,46 +82,20 @@ export type GoogleDiscoveryExtensionFailure = · 58 unmodified lines |
| 86 | | GoogleDiscoverySourceError | 82 | | GoogleDiscoverySourceError |
| 87 | | StorageFailure; | 83 | | StorageFailure; |
| 88 | | 84 | |
| 89 | export interface GoogleDiscoveryPluginExtension { | | |
| 90 | readonly probeDiscovery: ( | | |
| 91 | input: string | GoogleDiscoveryProbeInput, | | |
| 92 | ) => Effect.Effect< | | |
| 93 | GoogleDiscoveryProbeResult, | | |
| 94 | GoogleDiscoveryParseError | GoogleDiscoverySourceError | | |
| 95 | >; | | |
| 96 | readonly addSource: ( | | |
| 97 | input: GoogleDiscoveryAddSourceInput, | | |
| 98 | ) => Effect.Effect< | | |
| 99 | { readonly toolCount: number; readonly namespace: string }, | | |
| 100 | GoogleDiscoveryParseError | GoogleDiscoverySourceError | StorageFailure | | |
| 101 | >; | | |
| 102 | readonly removeSource: (namespace: string, scope: string) => Effect.Effect<void, StorageFailure>; | | |
| 103 | readonly getSource: ( | | |
| 104 | namespace: string, | | |
| 105 | scope: string, | | |
| 106 | ) => Effect.Effect<GoogleDiscoveryStoredSource | null, StorageFailure>; | | |
| 107 | readonly updateSource: ( | | |
| 108 | namespace: string, | | |
| 109 | scope: string, | | |
| 110 | input: GoogleDiscoveryUpdateSourceInput, | | |
| 111 | ) => Effect.Effect<void, StorageFailure>; | | |
| 112 | } | | |
| 113 | | | |
| 114 | // --------------------------------------------------------------------------- | 85 | // --------------------------------------------------------------------------- |
| 115 | // URL normalization + slug helpers (unchanged) | 86 | // URL normalization + slug helpers (unchanged) |
| 116 | // --------------------------------------------------------------------------- | 87 | // --------------------------------------------------------------------------- |
| 117 | | 88 | |
| 118 | const DISCOVERY_SERVICE_HOST = "https://www.googleapis.com/discovery/v1/apis"; | 89 | const DISCOVERY_SERVICE_HOST = "https://www.googleapis.com/discovery/v1/apis"; |
| | 90 | const decodeString = Schema.decodeUnknownSync(Schema.String); |
| | 91 | const isGoogleDiscoverySourceError = (error: unknown): error is GoogleDiscoverySourceError => |
| | 92 | Predicate.isTagged("GoogleDiscoverySourceError")(error); |
| 119 | | 93 | |
| 120 | const normalizeDiscoveryUrl = (discoveryUrl: string): string => { | 94 | const normalizeDiscoveryUrl = (discoveryUrl: string): string => { |
| 121 | const trimmed = discoveryUrl.trim(); | 95 | const trimmed = discoveryUrl.trim(); |
| 122 | if (trimmed.length === 0) return trimmed; | 96 | if (trimmed.length === 0) return trimmed; |
| 123 | let parsed: URL; | 97 | if (!URL.canParse(trimmed)) return trimmed; |
| 124 | try { | 98 | const parsed = new URL(trimmed); |
| 125 | parsed = new URL(trimmed); | | |
| 126 | } catch { | | |
| 127 | return trimmed; | | |
| 128 | } | | |
| 129 | if (parsed.pathname !== "/$discovery/rest") return trimmed; | 99 | if (parsed.pathname !== "/$discovery/rest") return trimmed; |
| 130 | const version = parsed.searchParams.get("version")?.trim(); | 100 | const version = parsed.searchParams.get("version")?.trim(); |
| 131 | if (!version) return trimmed; | 101 | if (!version) return trimmed; |
| @@ -164,7 +134,7 @@ const resolveGoogleDiscoveryCredentials = ( · 32 unmodified lines |
| 164 | }), | 134 | }), |
| 165 | }).pipe( | 135 | }).pipe( |
| 166 | Effect.mapError((err) => | 136 | Effect.mapError((err) => |
| 167 | err instanceof GoogleDiscoverySourceError | 137 | isGoogleDiscoverySourceError(err) |
| 168 | ? err | 138 | ? err |
| 169 | : new GoogleDiscoverySourceError({ message: "Secret resolution failed" }), | 139 | : new GoogleDiscoverySourceError({ message: "Secret resolution failed" }), |
| 170 | ), | 140 | ), |
| @@ -182,7 +152,7 @@ const resolveGoogleDiscoveryCredentials = ( · 11 unmodified lines |
| 182 | }), | 152 | }), |
| 183 | }).pipe( | 153 | }).pipe( |
| 184 | Effect.mapError((err) => | 154 | Effect.mapError((err) => |
| 185 | err instanceof GoogleDiscoverySourceError | 155 | isGoogleDiscoverySourceError(err) |
| 186 | ? err | 156 | ? err |
| 187 | : new GoogleDiscoverySourceError({ message: "Secret resolution failed" }), | 157 | : new GoogleDiscoverySourceError({ message: "Secret resolution failed" }), |
| 188 | ), | 158 | ), |
| @@ -200,29 +170,35 @@ const fetchDiscoveryDocument = ( · 11 unmodified lines |
| 200 | readonly queryParams?: Record<string, string>; | 170 | readonly queryParams?: Record<string, string>; |
| 201 | }, | 171 | }, |
| 202 | ) => | 172 | ) => |
| 203 | Effect.tryPromise({ | 173 | Effect.gen(function* () { |
| 204 | try: async () => { | 174 | const response = yield* Effect.tryPromise({ |
| 205 | const url = new URL(normalizeDiscoveryUrl(discoveryUrl)); | 175 | try: () => { |
| 206 | for (const [key, value] of Object.entries(credentials?.queryParams ?? {})) { | 176 | const url = new URL(normalizeDiscoveryUrl(discoveryUrl)); |
| 207 | url.searchParams.set(key, value); | 177 | for (const [key, value] of Object.entries(credentials?.queryParams ?? {})) { |
| 208 | } | 178 | url.searchParams.set(key, value); |
| 209 | const response = await fetch(url.toString(), { | 179 | } |
| 210 | headers: credentials?.headers, | 180 | return fetch(url.toString(), { |
| 211 | signal: AbortSignal.timeout(20_000), | 181 | headers: credentials?.headers, |
| 212 | }); | 182 | signal: AbortSignal.timeout(20_000), |
| 213 | if (!response.ok) { | | |
| 214 | throw new GoogleDiscoverySourceError({ | | |
| 215 | message: `Google Discovery fetch failed with status ${response.status}`, | | |
| 216 | }); | 183 | }); |
| 217 | } | 184 | }, |
| 218 | return response.text(); | 185 | catch: () => |
| 219 | }, | 186 | new GoogleDiscoverySourceError({ |
| 220 | catch: (cause) => | 187 | message: "Google Discovery fetch failed", |
| 221 | cause instanceof GoogleDiscoverySourceError | 188 | }), |
| 222 | ? cause | 189 | }); |
| 223 | : new GoogleDiscoverySourceError({ | 190 | if (!response.ok) { |
| 224 | message: cause instanceof Error ? cause.message : String(cause), | 191 | return yield* new GoogleDiscoverySourceError({ |
| 225 | }), | 192 | message: `Google Discovery fetch failed with status ${response.status}`, |
| | 193 | }); |
| | 194 | } |
| | 195 | return yield* Effect.tryPromise({ |
| | 196 | try: () => response.text(), |
| | 197 | catch: () => |
| | 198 | new GoogleDiscoverySourceError({ |
| | 199 | message: "Google Discovery response body read failed", |
| | 200 | }), |
| | 201 | }); |
| 226 | }); | 202 | }); |
| 227 | | 203 | |
| 228 | const normalizeSlug = (value: string): string => | 204 | const normalizeSlug = (value: string): string => |
| @@ -302,6 +278,97 @@ const registerManifest = ( · 73 unmodified lines |
| 302 | return manifest.methods.length; | 278 | return manifest.methods.length; |
| 303 | }); | 279 | }); |
| 304 | | 280 | |
| | 281 | const makeGoogleDiscoveryPluginExtension = (ctx: PluginCtx<GoogleDiscoveryStore>) => ({ |
| | 282 | probeDiscovery: (input: string | GoogleDiscoveryProbeInput) => |
| | 283 | Effect.gen(function* () { |
| | 284 | const discoveryUrl = typeof input === "string" ? input : input.discoveryUrl; |
| | 285 | const credentials = |
| | 286 | typeof input === "string" |
| | 287 | ? undefined |
| | 288 | : yield* resolveGoogleDiscoveryCredentials(input.credentials, ctx); |
| | 289 | const text = yield* fetchDiscoveryDocument(discoveryUrl, credentials); |
| | 290 | const manifest = yield* extractGoogleDiscoveryManifest(text); |
| | 291 | const scopes = Object.keys( |
| | 292 | Option.isSome(manifest.oauthScopes) ? manifest.oauthScopes.value : {}, |
| | 293 | ).sort(); |
| | 294 | const operations = manifest.methods.map((method) => ({ |
| | 295 | toolPath: method.toolPath, |
| | 296 | method: method.binding.method, |
| | 297 | pathTemplate: method.binding.pathTemplate, |
| | 298 | description: Option.isSome(method.description) ? method.description.value : null, |
| | 299 | })); |
| | 300 | return { |
| | 301 | name: Option.isSome(manifest.title) |
| | 302 | ? manifest.title.value |
| | 303 | : `${manifest.service} ${manifest.version}`, |
| | 304 | title: Option.isSome(manifest.title) ? manifest.title.value : null, |
| | 305 | service: manifest.service, |
| | 306 | version: manifest.version, |
| | 307 | toolCount: manifest.methods.length, |
| | 308 | scopes, |
| | 309 | operations, |
| | 310 | }; |
| | 311 | }), |
| | 312 | |
| | 313 | addSource: (input: GoogleDiscoveryAddSourceInput) => |
| | 314 | ctx.transaction( |
| | 315 | Effect.gen(function* () { |
| | 316 | const credentials = yield* resolveGoogleDiscoveryCredentials(input.credentials, ctx); |
| | 317 | const text = yield* fetchDiscoveryDocument(input.discoveryUrl, credentials); |
| | 318 | const manifest = yield* extractGoogleDiscoveryManifest(text); |
| | 319 | const namespace = |
| | 320 | input.namespace ?? |
| | 321 | deriveNamespace({ |
| | 322 | name: input.name, |
| | 323 | service: manifest.service, |
| | 324 | version: manifest.version, |
| | 325 | }); |
| | 326 | const sourceData = new GoogleDiscoveryStoredSourceDataSchema({ |
| | 327 | name: input.name, |
| | 328 | discoveryUrl: normalizeDiscoveryUrl(input.discoveryUrl), |
| | 329 | credentials: input.credentials, |
| | 330 | service: manifest.service, |
| | 331 | version: manifest.version, |
| | 332 | rootUrl: manifest.rootUrl, |
| | 333 | servicePath: manifest.servicePath, |
| | 334 | auth: input.auth, |
| | 335 | }); |
| | 336 | const toolCount = yield* registerManifest( |
| | 337 | ctx, |
| | 338 | namespace, |
| | 339 | input.scope, |
| | 340 | manifest, |
| | 341 | sourceData, |
| | 342 | ); |
| | 343 | return { toolCount, namespace }; |
| | 344 | }), |
| | 345 | ), |
| | 346 | |
| | 347 | removeSource: (namespace: string, scope: string) => |
| | 348 | ctx.transaction( |
| | 349 | Effect.gen(function* () { |
| | 350 | yield* ctx.storage.removeBindingsBySource(namespace, scope); |
| | 351 | yield* ctx.storage.removeSource(namespace, scope); |
| | 352 | yield* ctx.core.sources.unregister(namespace).pipe(Effect.ignore); |
| | 353 | }), |
| | 354 | ), |
| | 355 | |
| | 356 | // OAuth start/complete live on `ctx.oauth` now — the UI calls |
| | 357 | // the shared `/scopes/:scopeId/oauth/*` endpoints directly with a |
| | 358 | // Google-specific `authorization-code` strategy and writes the |
| | 359 | // resulting connection back via `updateSource`. |
| | 360 | |
| | 361 | getSource: (namespace: string, scope: string) => ctx.storage.getSource(namespace, scope), |
| | 362 | |
| | 363 | updateSource: (namespace: string, scope: string, input: GoogleDiscoveryUpdateSourceInput) => |
| | 364 | ctx.storage.updateSourceMeta(namespace, scope, { |
| | 365 | name: input.name?.trim() || undefined, |
| | 366 | auth: input.auth, |
| | 367 | }), |
| | 368 | }); |
| | 369 | |
| | 370 | export type GoogleDiscoveryPluginExtension = ReturnType<typeof makeGoogleDiscoveryPluginExtension>; |
| | 371 | |
| 305 | // --------------------------------------------------------------------------- | 372 | // --------------------------------------------------------------------------- |
| 306 | // Plugin | 373 | // Plugin |
| 307 | // --------------------------------------------------------------------------- | 374 | // --------------------------------------------------------------------------- |
| @@ -312,101 +379,13 @@ export const googleDiscoveryPlugin = definePlugin(() => ({ · 4 unmodified lines |
| 312 | schema: googleDiscoverySchema, | 379 | schema: googleDiscoverySchema, |
| 313 | storage: (deps) => makeGoogleDiscoveryStore(deps), | 380 | storage: (deps) => makeGoogleDiscoveryStore(deps), |
| 314 | | 381 | |
| 315 | extension: (ctx) => | 382 | extension: makeGoogleDiscoveryPluginExtension, |
| 316 | ({ | | |
| 317 | probeDiscovery: (input) => | | |
| 318 | Effect.gen(function* () { | | |
| 319 | const discoveryUrl = typeof input === "string" ? input : input.discoveryUrl; | | |
| 320 | const credentials = | | |
| 321 | typeof input === "string" | | |
| 322 | ? undefined | | |
| 323 | : yield* resolveGoogleDiscoveryCredentials(input.credentials, ctx); | | |
| 324 | const text = yield* fetchDiscoveryDocument(discoveryUrl, credentials); | | |
| 325 | const manifest = yield* extractGoogleDiscoveryManifest(text); | | |
| 326 | const scopes = Object.keys( | | |
| 327 | Option.isSome(manifest.oauthScopes) ? manifest.oauthScopes.value : {}, | | |
| 328 | ).sort(); | | |
| 329 | const operations = manifest.methods.map((method) => ({ | | |
| 330 | toolPath: method.toolPath, | | |
| 331 | method: method.binding.method, | | |
| 332 | pathTemplate: method.binding.pathTemplate, | | |
| 333 | description: Option.isSome(method.description) ? method.description.value : null, | | |
| 334 | })); | | |
| 335 | return { | | |
| 336 | name: Option.isSome(manifest.title) | | |
| 337 | ? manifest.title.value | | |
| 338 | : `${manifest.service} ${manifest.version}`, | | |
| 339 | title: Option.isSome(manifest.title) ? manifest.title.value : null, | | |
| 340 | service: manifest.service, | | |
| 341 | version: manifest.version, | | |
| 342 | toolCount: manifest.methods.length, | | |
| 343 | scopes, | | |
| 344 | operations, | | |
| 345 | }; | | |
| 346 | }), | | |
| 347 | | | |
| 348 | addSource: (input) => | | |
| 349 | ctx.transaction( | | |
| 350 | Effect.gen(function* () { | | |
| 351 | const credentials = yield* resolveGoogleDiscoveryCredentials(input.credentials, ctx); | | |
| 352 | const text = yield* fetchDiscoveryDocument(input.discoveryUrl, credentials); | | |
| 353 | const manifest = yield* extractGoogleDiscoveryManifest(text); | | |
| 354 | const namespace = | | |
| 355 | input.namespace ?? | | |
| 356 | deriveNamespace({ | | |
| 357 | name: input.name, | | |
| 358 | service: manifest.service, | | |
| 359 | version: manifest.version, | | |
| 360 | }); | | |
| 361 | const sourceData = new GoogleDiscoveryStoredSourceDataSchema({ | | |
| 362 | name: input.name, | | |
| 363 | discoveryUrl: normalizeDiscoveryUrl(input.discoveryUrl), | | |
| 364 | credentials: input.credentials, | | |
| 365 | service: manifest.service, | | |
| 366 | version: manifest.version, | | |
| 367 | rootUrl: manifest.rootUrl, | | |
| 368 | servicePath: manifest.servicePath, | | |
| 369 | auth: input.auth, | | |
| 370 | }); | | |
| 371 | const toolCount = yield* registerManifest( | | |
| 372 | ctx, | | |
| 373 | namespace, | | |
| 374 | input.scope, | | |
| 375 | manifest, | | |
| 376 | sourceData, | | |
| 377 | ); | | |
| 378 | return { toolCount, namespace }; | | |
| 379 | }), | | |
| 380 | ), | | |
| 381 | | | |
| 382 | removeSource: (namespace, scope) => | | |
| 383 | ctx.transaction( | | |
| 384 | Effect.gen(function* () { | | |
| 385 | yield* ctx.storage.removeBindingsBySource(namespace, scope); | | |
| 386 | yield* ctx.storage.removeSource(namespace, scope); | | |
| 387 | yield* ctx.core.sources.unregister(namespace).pipe(Effect.ignore); | | |
| 388 | }), | | |
| 389 | ), | | |
| 390 | | | |
| 391 | // OAuth start/complete live on `ctx.oauth` now — the UI calls | | |
| 392 | // the shared `/scopes/:scopeId/oauth/*` endpoints directly with a | | |
| 393 | // Google-specific `authorization-code` strategy and writes the | | |
| 394 | // resulting connection back via `updateSource`. | | |
| 395 | | | |
| 396 | getSource: (namespace, scope) => ctx.storage.getSource(namespace, scope), | | |
| 397 | | | |
| 398 | updateSource: (namespace, scope, input) => | | |
| 399 | ctx.storage.updateSourceMeta(namespace, scope, { | | |
| 400 | name: input.name?.trim() || undefined, | | |
| 401 | auth: input.auth, | | |
| 402 | }), | | |
| 403 | }) satisfies GoogleDiscoveryPluginExtension, | | |
| 404 | | 383 | |
| 405 | invokeTool: ({ ctx, toolRow, args }) => | 384 | invokeTool: ({ ctx, toolRow, args }) => |
| 406 | invokeGoogleDiscoveryTool({ | 385 | invokeGoogleDiscoveryTool({ |
| 407 | ctx: ctx as PluginCtx<GoogleDiscoveryStore>, | 386 | ctx: ctx as PluginCtx<GoogleDiscoveryStore>, |
| 408 | toolId: toolRow.id, | 387 | toolId: toolRow.id, |
| 409 | toolScope: toolRow.scope_id as string, | 388 | toolScope: decodeString(toolRow.scope_id), |
| 410 | args, | 389 | args, |
| 411 | }), | 390 | }), |
| 412 | | 391 | |
| @@ -414,7 +393,7 @@ export const googleDiscoveryPlugin = definePlugin(() => ({ · 1 unmodified line |
| 414 | Effect.gen(function* () { | 393 | Effect.gen(function* () { |
| 415 | const typedCtx = ctx as PluginCtx<GoogleDiscoveryStore>; | 394 | const typedCtx = ctx as PluginCtx<GoogleDiscoveryStore>; |
| 416 | const scopes = new Set<string>(); | 395 | const scopes = new Set<string>(); |
| 417 | for (const row of toolRows) scopes.add(row.scope_id as string); | 396 | for (const row of toolRows) scopes.add(decodeString(row.scope_id)); |
| 418 | const byScope = new Map<string, ReadonlyMap<string, GoogleDiscoveryMethodBinding>>(); | 397 | const byScope = new Map<string, ReadonlyMap<string, GoogleDiscoveryMethodBinding>>(); |
| 419 | for (const scope of scopes) { | 398 | for (const scope of scopes) { |
| 420 | const bindings = yield* typedCtx.storage.getBindingsForSource(sourceId, scope); | 399 | const bindings = yield* typedCtx.storage.getBindingsForSource(sourceId, scope); |
| @@ -422,7 +401,7 @@ export const googleDiscoveryPlugin = definePlugin(() => ({ · 1 unmodified line |
| 422 | } | 401 | } |
| 423 | const out: Record<string, ToolAnnotations> = {}; | 402 | const out: Record<string, ToolAnnotations> = {}; |
| 424 | for (const row of toolRows) { | 403 | for (const row of toolRows) { |
| 425 | const binding = byScope.get(row.scope_id as string)?.get(row.id); | 404 | const binding = byScope.get(decodeString(row.scope_id))?.get(row.id); |
| 426 | if (binding) { | 405 | if (binding) { |
| 427 | out[row.id] = annotationsForOperation(binding.method, binding.pathTemplate); | 406 | out[row.id] = annotationsForOperation(binding.method, binding.pathTemplate); |
| 428 | } | 407 | } |
| @@ -443,16 +422,11 @@ export const googleDiscoveryPlugin = definePlugin(() => ({ · 14 unmodified lines |
| 443 | usagesForSecret: ({ ctx, args }) => | 422 | usagesForSecret: ({ ctx, args }) => |
| 444 | Effect.gen(function* () { | 423 | Effect.gen(function* () { |
| 445 | const typedCtx = ctx as PluginCtx<GoogleDiscoveryStore>; | 424 | const typedCtx = ctx as PluginCtx<GoogleDiscoveryStore>; |
| 446 | const sources = yield* typedCtx.storage.findSourcesBySecret( | 425 | const sources = yield* typedCtx.storage.findSourcesBySecret(args.secretId); |
| 447 | args.secretId, | 426 | const childRows = yield* typedCtx.storage.findCredentialRowsBySecret(args.secretId); |
| 448 | ); | | |
| 449 | const childRows = yield* typedCtx.storage.findCredentialRowsBySecret( | | |
| 450 | args.secretId, | | |
| 451 | ); | | |
| 452 | const sourceKeys = new Set<string>(); | 427 | const sourceKeys = new Set<string>(); |
| 453 | for (const s of sources) sourceKeys.add(`${s.scope_id}:${s.namespace}`); | 428 | for (const s of sources) sourceKeys.add(`${s.scope_id}:${s.namespace}`); |
| 454 | for (const r of childRows) | 429 | for (const r of childRows) sourceKeys.add(`${r.scope_id}:${r.source_id}`); |
| 455 | sourceKeys.add(`${r.scope_id}:${r.source_id}`); | | |
| 456 | const names = yield* typedCtx.storage.lookupSourceNames([...sourceKeys]); | 430 | const names = yield* typedCtx.storage.lookupSourceNames([...sourceKeys]); |
| 457 | | 431 | |
| 458 | const out: Usage[] = []; | 432 | const out: Usage[] = []; |
| @@ -486,9 +460,7 @@ export const googleDiscoveryPlugin = definePlugin(() => ({ · 27 unmodified lines |
| 486 | usagesForConnection: ({ ctx, args }) => | 460 | usagesForConnection: ({ ctx, args }) => |
| 487 | Effect.gen(function* () { | 461 | Effect.gen(function* () { |
| 488 | const typedCtx = ctx as PluginCtx<GoogleDiscoveryStore>; | 462 | const typedCtx = ctx as PluginCtx<GoogleDiscoveryStore>; |
| 489 | const sources = yield* typedCtx.storage.findSourcesByConnection( | 463 | const sources = yield* typedCtx.storage.findSourcesByConnection(args.connectionId); |
| 490 | args.connectionId, | | |
| 491 | ); | | |
| 492 | return sources.map( | 464 | return sources.map( |
| 493 | (s) => | 465 | (s) => |
| 494 | new Usage({ | 466 | new Usage({ |
| @@ -563,7 +535,7 @@ export const googleDiscoveryPlugin = definePlugin(() => ({ · 68 unmodified lines |
| 563 | servicePath: manifest.servicePath, | 535 | servicePath: manifest.servicePath, |
| 564 | }); | 536 | }); |
| 565 | yield* registerManifest(typedCtx, sourceId, scope, manifest, next); | 537 | yield* registerManifest(typedCtx, sourceId, scope, manifest, next); |
| 566 | }).pipe(Effect.mapError((err) => (err instanceof Error ? err : new Error(String(err))))), | 538 | }), |
| 567 | | 539 | |
| 568 | // Connection refresh is owned by the canonical `"oauth2"` | 540 | // Connection refresh is owned by the canonical `"oauth2"` |
| 569 | // ConnectionProvider registered by core — no plugin-specific handler | 541 | // ConnectionProvider registered by core — no plugin-specific handler |