(
slug: IntegrationSlug,
)
| 3366 | }); |
| 3367 | |
| 3368 | const integrationsRemove = ( |
| 3369 | slug: IntegrationSlug, |
| 3370 | ): Effect.Effect< |
| 3371 | void, |
| 3372 | IntegrationRemovalNotAllowedError | OrgWriteDeniedError | StorageFailure |
| 3373 | > => |
| 3374 | transaction( |
| 3375 | Effect.gen(function* () { |
| 3376 | yield* guardOrgWrite(); |
| 3377 | const existing = yield* findIntegrationRow(slug); |
| 3378 | if (!existing) return null; |
| 3379 | if (!existing.can_remove) { |
| 3380 | return yield* new IntegrationRemovalNotAllowedError({ slug }); |
| 3381 | } |
| 3382 | const runtime = runtimes.get(existing.plugin_id); |
| 3383 | if (runtime?.plugin.removeIntegration) { |
| 3384 | const authMethods = yield* describeAuthMethodsForRow(existing); |
| 3385 | yield* runtime.plugin |
| 3386 | .removeIntegration({ |
| 3387 | ctx: runtime.ctx, |
| 3388 | integration: rowToIntegrationRecord(existing, authMethods), |
| 3389 | }) |
| 3390 | .pipe( |
| 3391 | Effect.mapError((cause) => |
| 3392 | pluginStorageFailure(existing.plugin_id, "removeIntegration", cause), |
| 3393 | ), |
| 3394 | ); |
| 3395 | } |
| 3396 | // The catalog row goes first through the bound handle: a read-only |
| 3397 | // (platform-view) context is refused here, before the widened |
| 3398 | // cascade below could touch anything. |
| 3399 | yield* core.deleteMany("integration", { |
| 3400 | where: (b: AnyCb) => b("slug", "=", String(slug)), |
| 3401 | }); |
| 3402 | // Drop connections / tools / definitions for this integration across |
| 3403 | // EVERY subject in the tenant, not just the remover's own rows. A |
| 3404 | // removed integration has no reason to keep anyone's rows, and rows |
| 3405 | // left behind become orphans: invisible in the catalog, yet still |
| 3406 | // listed to agents and still targetable by reconnect. |
| 3407 | const where = (b: AnyCb) => b("integration", "=", String(slug)); |
| 3408 | yield* cascadeCore.deleteMany("tool", { where }); |
| 3409 | yield* cascadeCore.deleteMany("definition", { where }); |
| 3410 | yield* cascadeCore.deleteMany("connection", { where }); |
| 3411 | return existing.plugin_id; |
| 3412 | }), |
| 3413 | ).pipe( |
| 3414 | Effect.tap((removedPluginId) => |
| 3415 | removedPluginId !== null |
| 3416 | ? notifyIntegrationChange({ |
| 3417 | kind: "removed", |
| 3418 | pluginKey: removedPluginId, |
| 3419 | slug, |
| 3420 | }) |
| 3421 | : Effect.void, |
| 3422 | ), |
| 3423 | Effect.asVoid, |
| 3424 | ); |
| 3425 |
no test coverage detected