From 98a3dfd81a1672e3bde60f8d119be84c4d09a222 Mon Sep 17 00:00:00 2001 From: Mahesh Sanikommmu Date: Tue, 10 Mar 2026 12:58:41 +0530 Subject: [PATCH] feat: delete connection without removing the documents --- apps/docs/connectors/github.mdx | 2 +- apps/docs/connectors/overview.mdx | 27 ++- apps/docs/connectors/s3.mdx | 2 +- .../components/add-document/connections.tsx | 53 +++- .../integrations/connections-detail.tsx | 49 +++- .../components/remove-connection-dialog.tsx | 226 ++++++++++++++++++ .../components/settings/connections-mcp.tsx | 50 +++- packages/lib/api.ts | 3 + 8 files changed, 384 insertions(+), 28 deletions(-) create mode 100644 apps/web/components/remove-connection-dialog.tsx diff --git a/apps/docs/connectors/github.mdx b/apps/docs/connectors/github.mdx index 68df9a4f0..82a547f7d 100644 --- a/apps/docs/connectors/github.mdx +++ b/apps/docs/connectors/github.mdx @@ -448,7 +448,7 @@ Deleting a GitHub connection will: - Stop all future syncs from configured repositories - Remove all webhooks from the repositories - Revoke the OAuth authorization -- **Permanently delete all synced documents** from your Supermemory knowledge base +- **Permanently delete all synced documents** from your Supermemory knowledge base (unless you pass `deleteDocuments=false` as a query parameter to keep them) ### Manual Sync diff --git a/apps/docs/connectors/overview.mdx b/apps/docs/connectors/overview.mdx index a79b253c2..c0f98cf4d 100644 --- a/apps/docs/connectors/overview.mdx +++ b/apps/docs/connectors/overview.mdx @@ -287,6 +287,16 @@ curl -X POST "https://api.supermemory.ai/v3/connections/list" \ ### Delete Connections +The `DELETE /v3/connections/:connectionId` endpoint accepts an optional `deleteDocuments` query parameter: + +| Parameter | Type | Default | Description | +| --- | --- | --- | --- | +| `deleteDocuments` | boolean | `true` | When `true`, all documents imported by the connection are permanently deleted. When `false`, the connection is removed but documents are kept. | + + +Setting `deleteDocuments=false` is useful when you want to disconnect an integration without losing the memories that were already imported. + + ```typescript Typescript @@ -296,9 +306,14 @@ const client = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY! }); -// Delete by connection ID +// Delete connection and all imported documents (default) const result = await client.connections.deleteByID(connectionId); +// Delete connection but keep imported documents +const result = await client.connections.deleteByID(connectionId, { + deleteDocuments: false +}); + // Or delete by provider (requires container tags) const result = await client.connections.deleteByProvider('notion', { containerTags: ['user-123'] @@ -314,9 +329,12 @@ import os client = Supermemory(api_key=os.environ.get("SUPERMEMORY_API_KEY")) -# Delete by connection ID +# Delete connection and all imported documents (default) result = client.connections.delete_by_id(connection_id) +# Delete connection but keep imported documents +result = client.connections.delete_by_id(connection_id, delete_documents=False) + # Or delete by provider (requires container tags) result = client.connections.delete_by_provider( provider='notion', @@ -328,9 +346,14 @@ print(f"Deleted: {result.id} {result.provider}") ``` ```bash cURL +# Delete connection and all imported documents (default) curl -X DELETE "https://api.supermemory.ai/v3/connections/conn_abc123" \ -H "Authorization: Bearer $SUPERMEMORY_API_KEY" +# Delete connection but keep imported documents +curl -X DELETE "https://api.supermemory.ai/v3/connections/conn_abc123?deleteDocuments=false" \ + -H "Authorization: Bearer $SUPERMEMORY_API_KEY" + # Response: { # "id": "conn_abc123", # "provider": "notion" diff --git a/apps/docs/connectors/s3.mdx b/apps/docs/connectors/s3.mdx index 2cca82b11..90d99905b 100644 --- a/apps/docs/connectors/s3.mdx +++ b/apps/docs/connectors/s3.mdx @@ -153,7 +153,7 @@ The regex must contain a named capture group `(?...)` and be less than 2 -Deleting a connection removes all synced documents from Supermemory. +By default, deleting a connection removes all synced documents from Supermemory. To keep documents, pass `deleteDocuments=false` as a query parameter: `DELETE /v3/connections/:id?deleteDocuments=false` ### Manual Sync diff --git a/apps/web/components/add-document/connections.tsx b/apps/web/components/add-document/connections.tsx index c6d9f00a5..be0d279a2 100644 --- a/apps/web/components/add-document/connections.tsx +++ b/apps/web/components/add-document/connections.tsx @@ -13,6 +13,7 @@ import type { z } from "zod" import { dmSansClassName } from "@/lib/fonts" import { cn } from "@lib/utils" import { Button } from "@ui/components/button" +import { RemoveConnectionDialog } from "@/components/remove-connection-dialog" type Connection = z.infer @@ -54,6 +55,10 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) { const [connectingProvider, setConnectingProvider] = useState(null) const [isUpgrading, setIsUpgrading] = useState(false) + const [removeDialog, setRemoveDialog] = useState<{ + open: boolean + connection: Connection | null + }>({ open: false, connection: null }) // Check Pro status useEffect(() => { @@ -159,15 +164,26 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) { }, }) - // Disconnect mutation const deleteConnectionMutation = useMutation({ - mutationFn: async (connectionId: string) => { - await $fetch(`@delete/connections/${connectionId}`) + mutationFn: async ({ + connectionId, + deleteDocuments, + }: { + connectionId: string + deleteDocuments: boolean + }) => { + await $fetch(`@delete/connections/${connectionId}`, { + query: { deleteDocuments }, + }) + return { deleteDocuments } }, - onSuccess: () => { + onSuccess: (_data, variables) => { toast.success( - "Connection removal has started. supermemory will permanently delete all documents related to the connection in the next few minutes.", + variables.deleteDocuments + ? "Connection removal has started. supermemory will permanently delete all documents related to the connection in the next few minutes." + : "Connection removed. Your memories have been kept.", ) + setRemoveDialog({ open: false, connection: null }) queryClient.invalidateQueries({ queryKey: ["connections"] }) }, onError: (error) => { @@ -182,8 +198,8 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) { addConnectionMutation.mutate(provider) } - const handleDisconnect = (connectionId: string) => { - deleteConnectionMutation.mutate(connectionId) + const handleDisconnect = (connection: Connection) => { + setRemoveDialog({ open: true, connection }) } const hasConnections = connections.length > 0 @@ -278,7 +294,7 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) { + + + + +
+ + +
+ + + + ) +} diff --git a/apps/web/components/settings/connections-mcp.tsx b/apps/web/components/settings/connections-mcp.tsx index 12fafc457..bb0da394a 100644 --- a/apps/web/components/settings/connections-mcp.tsx +++ b/apps/web/components/settings/connections-mcp.tsx @@ -15,6 +15,7 @@ import type { z } from "zod" import { analytics } from "@/lib/analytics" import { ConnectAIModal } from "@/components/connect-ai-modal" import { AddDocumentModal } from "@/components/add-document" +import { RemoveConnectionDialog } from "@/components/remove-connection-dialog" import { DEFAULT_PROJECT_ID } from "@lib/constants" import type { Project } from "@lib/types" @@ -335,6 +336,10 @@ export default function ConnectionsMCP() { const autumn = useCustomer() const [isAddDocumentOpen, setIsAddDocumentOpen] = useState(false) const [mcpModalOpen, setMcpModalOpen] = useState(false) + const [removeDialog, setRemoveDialog] = useState<{ + open: boolean + connection: Connection | null + }>({ open: false, connection: null }) const projects = (queryClient.getQueryData(["projects"]) || []) as Project[] @@ -392,16 +397,27 @@ export default function ConnectionsMCP() { } }, [connectionsError]) - // Delete connection mutation const deleteConnectionMutation = useMutation({ - mutationFn: async (connectionId: string) => { - await $fetch(`@delete/connections/${connectionId}`) + mutationFn: async ({ + connectionId, + deleteDocuments, + }: { + connectionId: string + deleteDocuments: boolean + }) => { + await $fetch(`@delete/connections/${connectionId}`, { + query: { deleteDocuments }, + }) + return { deleteDocuments } }, - onSuccess: () => { + onSuccess: (_data, variables) => { analytics.connectionDeleted() toast.success( - "Connection removal has started. Supermemory will permanently delete the documents in the next few minutes.", + variables.deleteDocuments + ? "Connection removal has started. Supermemory will permanently delete the documents in the next few minutes." + : "Connection removed. Your memories have been kept.", ) + setRemoveDialog({ open: false, connection: null }) queryClient.invalidateQueries({ queryKey: ["connections"] }) }, onError: (error) => { @@ -478,9 +494,7 @@ export default function ConnectionsMCP() { - deleteConnectionMutation.mutate(connection.id) - } + onDelete={() => setRemoveDialog({ open: true, connection })} isDeleting={deleteConnectionMutation.isPending} disabled={!hasProProduct} projects={projects} @@ -563,6 +577,26 @@ export default function ConnectionsMCP() { onClose={() => setIsAddDocumentOpen(false)} defaultTab="connect" /> + + { + if (!open) setRemoveDialog({ open: false, connection: null }) + }} + provider={removeDialog.connection?.provider} + documentCount={ + (removeDialog.connection?.metadata?.documentCount as number) ?? 0 + } + onConfirm={(deleteDocuments) => { + if (removeDialog.connection) { + deleteConnectionMutation.mutate({ + connectionId: removeDialog.connection.id, + deleteDocuments, + }) + } + }} + isDeleting={deleteConnectionMutation.isPending} + /> ) } diff --git a/packages/lib/api.ts b/packages/lib/api.ts index e40992411..cd21db767 100644 --- a/packages/lib/api.ts +++ b/packages/lib/api.ts @@ -117,6 +117,9 @@ export const apiSchema = createSchema({ provider: z.string(), }), params: z.object({ connectionId: z.string() }), + query: z.object({ + deleteDocuments: z.boolean().optional(), + }), }, // Settings operations