From 05cbf9607e547a5740a4e1d69b8e3aebafb07398 Mon Sep 17 00:00:00 2001 From: Divyansh2992 Date: Sat, 28 Feb 2026 21:56:35 +0530 Subject: [PATCH 01/16] feat(console): add JSON export option to database documents --- src/lib/actions/analytics.ts | 2 + .../table-[table]/+page.svelte | 4 +- .../table-[table]/export-json/+page.svelte | 247 ++++++++++++++++++ .../table-[table]/export/+page.svelte | 183 +++++++++---- 4 files changed, 387 insertions(+), 49 deletions(-) create mode 100644 src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export-json/+page.svelte diff --git a/src/lib/actions/analytics.ts b/src/lib/actions/analytics.ts index 023e2fbeee..6cdc1760e4 100644 --- a/src/lib/actions/analytics.ts +++ b/src/lib/actions/analytics.ts @@ -156,6 +156,7 @@ export enum Click { DatabaseDatabaseDelete = 'click_database_delete', DatabaseImportCsv = 'click_database_import_csv', DatabaseExportCsv = 'click_database_export_csv', + DatabaseExportJson = 'click_database_export_json', DomainCreateClick = 'click_domain_create', DomainDeleteClick = 'click_domain_delete', DomainRetryDomainVerificationClick = 'click_domain_retry_domain_verification', @@ -283,6 +284,7 @@ export enum Submit { DatabaseUpdateName = 'submit_database_update_name', DatabaseImportCsv = 'submit_database_import_csv', DatabaseExportCsv = 'submit_database_export_csv', + DatabaseExportJson = 'submit_database_export_json', DatabaseBackupDelete = 'submit_database_backup_delete', DatabaseBackupPolicyCreate = 'submit_database_backup_policy_create', diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte index 6467241f2a..3edd803dab 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte @@ -152,6 +152,8 @@ return queryParam ? `${url}?query=${encodeURIComponent(queryParam)}` : url; } + + onDestroy(() => ($showCreateColumnSheet.show = false)); @@ -239,7 +241,7 @@ - Export CSV + Export diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export-json/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export-json/+page.svelte new file mode 100644 index 0000000000..a6c34ab86a --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export-json/+page.svelte @@ -0,0 +1,247 @@ + + + +
+ +
+ + + + + + + + + + + {#each visibleColumns as column (column.key)} +
+ +
+ {/each} +
+ + {#if hasMoreColumns} +
+ +
+ {/if} +
+
+ +
+ + +
+ +
+ + {#if localTags.length > 0} + + { + removeLocalFilter(e.detail); + }} /> + + {/if} +
+
+
+
+
+ + + + + + +
+ + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte index 9ed600090f..4b25b3b62d 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte @@ -16,6 +16,7 @@ import { toLocalDateTimeISO } from '$lib/helpers/date'; import { writable } from 'svelte/store'; import { isSmallViewport } from '$lib/stores/viewport'; + import { Query } from '@appwrite.io/console'; let showExitModal = $state(false); let formComponent: Form; @@ -29,7 +30,9 @@ .split('T') .join('_') .slice(0, -4); - const filename = `${$table.name}_${timestamp}.csv`; + + let exportFormat = $state<'csv' | 'json'>('csv'); + let filename = $derived(`${$table.name}_${timestamp}.${exportFormat}`); let selectedColumns = $state>({}); let showAllColumns = $state(false); @@ -97,34 +100,107 @@ return; } - try { - await sdk - .forProject(page.params.region, page.params.project) - .migrations.createCSVExport({ - resourceId: `${page.params.database}:${page.params.table}`, - filename: filename, - columns: selectedCols, - queries: exportWithFilters ? Array.from(localQueries.values()) : [], - delimiter: delimiterMap[delimiter], - header: includeHeader, - notify: true + if (exportFormat === 'csv') { + try { + await sdk + .forProject(page.params.region, page.params.project) + .migrations.createCSVExport({ + resourceId: `${page.params.database}:${page.params.table}`, + filename: filename, + columns: selectedCols, + queries: exportWithFilters ? Array.from(localQueries.values()) : [], + delimiter: delimiterMap[delimiter], + header: includeHeader, + notify: true + }); + + addNotification({ + type: 'success', + message: 'CSV export has started' }); - addNotification({ - type: 'success', - message: 'CSV export has started' - }); + trackEvent(Submit.DatabaseExportCsv); + await goto(tableUrl); + } catch (error) { + addNotification({ + type: 'error', + message: error.message + }); - trackEvent(Submit.DatabaseExportCsv); + trackError(error, Submit.DatabaseExportCsv); + } + } else { + // JSON export logic + $isSubmitting = true; + try { + const activeQueries = exportWithFilters ? Array.from(localQueries.values()) : []; + const allRows: Record[] = []; + const pageSize = 100; + let lastId: string | undefined = undefined; + let fetched = 0; + let total = Infinity; - await goto(tableUrl); - } catch (error) { - addNotification({ - type: 'error', - message: error.message - }); + while (fetched < total) { + const pageQueries = [ + Query.limit(pageSize), + ...activeQueries + ]; + + if (lastId) { + pageQueries.push(Query.cursorAfter(lastId)); + } + + const response = await sdk + .forProject(page.params.region, page.params.project) + .tablesDB.listRows({ + databaseId: page.params.database, + tableId: page.params.table, + queries: pageQueries + }); + + total = response.total; + + if (response.rows.length === 0) break; + + const filtered = response.rows.map((row) => { + const obj: Record = {}; + for (const col of selectedCols) { + obj[col] = row[col]; + } + return obj; + }); - trackError(error, Submit.DatabaseExportCsv); + allRows.push(...filtered); + fetched += response.rows.length; + lastId = response.rows[response.rows.length - 1].$id as string; + } + + const json = JSON.stringify(allRows, null, 2); + const blob = new Blob([json], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const anchor = document.createElement('a'); + anchor.href = url; + anchor.download = filename; + anchor.click(); + URL.revokeObjectURL(url); + + addNotification({ + type: 'success', + message: `JSON export complete — ${allRows.length} row${allRows.length !== 1 ? 's' : ''} downloaded` + }); + + trackEvent(Submit.DatabaseExportJson); + await goto(tableUrl); + } catch (error) { + addNotification({ + type: 'error', + message: error.message + }); + + trackError(error, Submit.DatabaseExportJson); + } finally { + $isSubmitting = false; + } } } @@ -134,7 +210,7 @@ }); - +
@@ -172,30 +248,41 @@
- - - - - Define how to separate values in the exported file. - - - - - - + { value: 'csv', label: 'CSV' }, + { value: 'json', label: 'JSON' } + ]} /> + + {#if exportFormat === 'csv'} + + + + + + Define how to separate values in the exported file. + + + + + + + {/if}
From 3a392e96ed2735b3dc27eb696ad170763521c417 Mon Sep 17 00:00:00 2001 From: Divyansh2992 Date: Sat, 28 Feb 2026 22:45:15 +0530 Subject: [PATCH 02/16] cleanup: remove redundant export-json wizard --- .../table-[table]/export-json/+page.svelte | 247 ------------------ 1 file changed, 247 deletions(-) delete mode 100644 src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export-json/+page.svelte diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export-json/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export-json/+page.svelte deleted file mode 100644 index a6c34ab86a..0000000000 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export-json/+page.svelte +++ /dev/null @@ -1,247 +0,0 @@ - - - - - -
- - - - - - - - - - - {#each visibleColumns as column (column.key)} -
- -
- {/each} -
- - {#if hasMoreColumns} -
- -
- {/if} -
-
- -
- - -
- -
- - {#if localTags.length > 0} - - { - removeLocalFilter(e.detail); - }} /> - - {/if} -
-
-
-
- - - - - - - -
- - From f7a82df2480f72a9370c9db49fbaa571ba2b21f9 Mon Sep 17 00:00:00 2001 From: Divyansh2992 Date: Sun, 1 Mar 2026 14:14:59 +0530 Subject: [PATCH 03/16] fix: update minimatch override to 10.2.3 to resolve ReDoS vulnerabilities --- bun.lock | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bun.lock b/bun.lock index 72b3384900..12377f9149 100644 --- a/bun.lock +++ b/bun.lock @@ -76,7 +76,7 @@ }, }, "overrides": { - "minimatch": "10.2.1", + "minimatch": "10.2.3", "vite": "npm:rolldown-vite@latest", }, "packages": { @@ -1086,7 +1086,7 @@ "min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="], - "minimatch": ["minimatch@10.2.1", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A=="], + "minimatch": ["minimatch@10.2.3", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], diff --git a/package.json b/package.json index 57e3e7a94a..2ecc76626c 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,6 @@ }, "overrides": { "vite": "npm:rolldown-vite@latest", - "minimatch": "10.2.1" + "minimatch": "10.2.3" } -} +} \ No newline at end of file From 6474248fc6aea835aa89b9ad80ef58bb9c4d4beb Mon Sep 17 00:00:00 2001 From: Divyansh2992 Date: Sun, 1 Mar 2026 17:52:38 +0530 Subject: [PATCH 04/16] style: run prettier to fix formatting and resolve lint errors --- package.json | 2 +- .../table-[table]/+page.svelte | 2 -- .../table-[table]/export/+page.svelte | 15 ++++++++------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 2ecc76626c..0ff0eb93c9 100644 --- a/package.json +++ b/package.json @@ -91,4 +91,4 @@ "vite": "npm:rolldown-vite@latest", "minimatch": "10.2.3" } -} \ No newline at end of file +} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte index 3edd803dab..a03d5157af 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte @@ -152,8 +152,6 @@ return queryParam ? `${url}?query=${encodeURIComponent(queryParam)}` : url; } - - onDestroy(() => ($showCreateColumnSheet.show = false)); diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte index 4b25b3b62d..e3e0fe810b 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte @@ -30,7 +30,7 @@ .split('T') .join('_') .slice(0, -4); - + let exportFormat = $state<'csv' | 'json'>('csv'); let filename = $derived(`${$table.name}_${timestamp}.${exportFormat}`); @@ -141,11 +141,8 @@ let total = Infinity; while (fetched < total) { - const pageQueries = [ - Query.limit(pageSize), - ...activeQueries - ]; - + const pageQueries = [Query.limit(pageSize), ...activeQueries]; + if (lastId) { pageQueries.push(Query.cursorAfter(lastId)); } @@ -267,7 +264,11 @@ { value: 'Tab', label: 'Tab' }, { value: 'Pipe', label: 'Pipe' } ]}> - + From 848930c4c9f856903ad65dd2ce0bf11afdb7f0e4 Mon Sep 17 00:00:00 2001 From: Divyansh2992 Date: Sun, 1 Mar 2026 18:14:40 +0530 Subject: [PATCH 05/16] fixed the minor bug --- .../database-[database]/table-[table]/export/+page.svelte | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte index e3e0fe810b..580dcaf3e0 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte @@ -178,8 +178,14 @@ const anchor = document.createElement('a'); anchor.href = url; anchor.download = filename; + document.body.appendChild(anchor); anchor.click(); - URL.revokeObjectURL(url); + + // Revoke the object URL after a short delay to ensure the browser has started the download + setTimeout(() => { + URL.revokeObjectURL(url); + document.body.removeChild(anchor); + }, 100); addNotification({ type: 'success', From 443d2441fb315ce29210e227298de848a81f3a0e Mon Sep 17 00:00:00 2001 From: Divyansh2992 Date: Sun, 1 Mar 2026 20:20:25 +0530 Subject: [PATCH 06/16] modified page.svelte --- .../table-[table]/export/+page.svelte | 104 +++++++++++++----- 1 file changed, 75 insertions(+), 29 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte index 580dcaf3e0..5298b393d3 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte @@ -21,6 +21,8 @@ let showExitModal = $state(false); let formComponent: Form; let isSubmitting = $state(writable(false)); + let abortController: AbortController | null = null; + let exportProgress = 0; let localQueries = $state>(new Map()); const localTags = $derived(Array.from(localQueries.keys())); @@ -130,8 +132,11 @@ trackError(error, Submit.DatabaseExportCsv); } } else { - // JSON export logic + trackEvent(Submit.DatabaseExportJson); // Track event at the start of JSON export $isSubmitting = true; + abortController = new AbortController(); // Initialize abort controller + exportProgress = 0; // Reset progress + try { const activeQueries = exportWithFilters ? Array.from(localQueries.values()) : []; const allRows: Record[] = []; @@ -140,7 +145,23 @@ let fetched = 0; let total = Infinity; + // Add a warning for potentially large exports + addNotification({ + type: 'info', + message: 'JSON export started. This may take a while for large datasets.', + timeout: 5000 // Keep it visible for a bit + }); + while (fetched < total) { + // Check for abort signal + if (abortController.signal.aborted) { + addNotification({ + type: 'warning', + message: 'JSON export cancelled.' + }); + break; // Exit the loop if aborted + } + const pageQueries = [Query.limit(pageSize), ...activeQueries]; if (lastId) { @@ -152,7 +173,8 @@ .tablesDB.listRows({ databaseId: page.params.database, tableId: page.params.table, - queries: pageQueries + queries: pageQueries, + signal: abortController.signal // Pass abort signal }); total = response.total; @@ -170,43 +192,61 @@ allRows.push(...filtered); fetched += response.rows.length; lastId = response.rows[response.rows.length - 1].$id as string; + exportProgress = Math.min(100, Math.floor((fetched / total) * 100)); // Update progress } - const json = JSON.stringify(allRows, null, 2); - const blob = new Blob([json], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const anchor = document.createElement('a'); - anchor.href = url; - anchor.download = filename; - document.body.appendChild(anchor); - anchor.click(); - - // Revoke the object URL after a short delay to ensure the browser has started the download - setTimeout(() => { - URL.revokeObjectURL(url); - document.body.removeChild(anchor); - }, 100); - - addNotification({ - type: 'success', - message: `JSON export complete — ${allRows.length} row${allRows.length !== 1 ? 's' : ''} downloaded` - }); + if (!abortController.signal.aborted) { + const json = JSON.stringify(allRows, null, 2); + const blob = new Blob([json], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const anchor = document.createElement('a'); + anchor.href = url; + anchor.download = filename; + document.body.appendChild(anchor); + anchor.click(); + + // Revoke the object URL after a short delay to ensure the browser has started the download + setTimeout(() => { + URL.revokeObjectURL(url); + document.body.removeChild(anchor); + }, 100); + + addNotification({ + type: 'success', + message: `JSON export complete — ${allRows.length} row${allRows.length !== 1 ? 's' : ''} downloaded` + }); - trackEvent(Submit.DatabaseExportJson); - await goto(tableUrl); + await goto(tableUrl); + } } catch (error) { - addNotification({ - type: 'error', - message: error.message - }); - - trackError(error, Submit.DatabaseExportJson); + if (error.name === 'AbortError') { + addNotification({ + type: 'warning', + message: 'JSON export cancelled by user.' + }); + } else { + addNotification({ + type: 'error', + message: error.message + }); + trackError(error, Submit.DatabaseExportJson); + } } finally { $isSubmitting = false; + exportProgress = 0; // Reset progress + abortController = null; // Clean up controller } } } + // Cancel the JSON export operation + function cancelExport() { + if (abortController) { + abortController.abort(); + addNotification({ type: 'info', message: 'JSON export cancellation requested.' }); + } + } + onMount(() => { initializeColumns(); localQueries = new Map($queries); @@ -215,6 +255,12 @@
+ {#if exportFormat === 'json' && $isSubmitting} +
+
+ +
+ {/if}
From 23ce56d49d5beb4fa17f8597c63e607af3e6fe67 Mon Sep 17 00:00:00 2001 From: Divyansh2992 Date: Sun, 1 Mar 2026 20:31:30 +0530 Subject: [PATCH 07/16] fi the adjustment --- .../database-[database]/table-[table]/export/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte index 5298b393d3..ee54d4c2b0 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte @@ -257,7 +257,7 @@ {#if exportFormat === 'json' && $isSubmitting}
-
+
{/if} From 0bb84d21b0d4254970cba945c6a448f07dd3fb1f Mon Sep 17 00:00:00 2001 From: Divyansh2992 Date: Sun, 1 Mar 2026 20:50:08 +0530 Subject: [PATCH 08/16] fixed progress bar --- .../database-[database]/table-[table]/export/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte index ee54d4c2b0..977b3b134e 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte @@ -257,7 +257,7 @@ {#if exportFormat === 'json' && $isSubmitting}
-
+
{/if} From eaf458b96a11dcf49f4e5cbd076c1a6fbb5cdf0d Mon Sep 17 00:00:00 2001 From: Divyansh2992 Date: Sun, 1 Mar 2026 21:11:37 +0530 Subject: [PATCH 09/16] fix changes --- .../table-[table]/export/+page.svelte | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte index 977b3b134e..fc416a70fc 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte @@ -12,7 +12,7 @@ import { table } from '../store'; import { queries, type TagValue } from '$lib/components/filters/store'; import { TagList } from '$lib/components/filters'; - import { Submit, trackEvent, trackError } from '$lib/actions/analytics'; + import { Click, Submit, trackEvent, trackError } from '$lib/actions/analytics'; import { toLocalDateTimeISO } from '$lib/helpers/date'; import { writable } from 'svelte/store'; import { isSmallViewport } from '$lib/stores/viewport'; @@ -173,8 +173,7 @@ .tablesDB.listRows({ databaseId: page.params.database, tableId: page.params.table, - queries: pageQueries, - signal: abortController.signal // Pass abort signal + queries: pageQueries }); total = response.total; @@ -257,7 +256,7 @@ {#if exportFormat === 'json' && $isSubmitting}
-
+
{/if} @@ -373,7 +372,12 @@ From 675cdbd82bf35d57ee33cba8698d1d800d879df4 Mon Sep 17 00:00:00 2001 From: Divyansh2992 Date: Sun, 1 Mar 2026 21:24:51 +0530 Subject: [PATCH 10/16] updated page.svelte --- .../table-[table]/export/+page.svelte | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte index fc416a70fc..6ac7587e1d 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte @@ -218,18 +218,11 @@ await goto(tableUrl); } } catch (error) { - if (error.name === 'AbortError') { - addNotification({ - type: 'warning', - message: 'JSON export cancelled by user.' - }); - } else { - addNotification({ - type: 'error', - message: error.message - }); - trackError(error, Submit.DatabaseExportJson); - } + addNotification({ + type: 'error', + message: error.message + }); + trackError(error, Submit.DatabaseExportJson); } finally { $isSubmitting = false; exportProgress = 0; // Reset progress @@ -242,7 +235,6 @@ function cancelExport() { if (abortController) { abortController.abort(); - addNotification({ type: 'info', message: 'JSON export cancellation requested.' }); } } From c9ebc6cd4b1a9c53a459d3a0a77af6635df03bbd Mon Sep 17 00:00:00 2001 From: Divyansh2992 Date: Sun, 1 Mar 2026 21:37:52 +0530 Subject: [PATCH 11/16] fix the error --- .../table-[table]/export/+page.svelte | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte index 6ac7587e1d..1bd9342410 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte @@ -22,7 +22,7 @@ let formComponent: Form; let isSubmitting = $state(writable(false)); let abortController: AbortController | null = null; - let exportProgress = 0; + let exportProgress = $state(0); let localQueries = $state>(new Map()); const localTags = $derived(Array.from(localQueries.keys())); @@ -132,7 +132,6 @@ trackError(error, Submit.DatabaseExportCsv); } } else { - trackEvent(Submit.DatabaseExportJson); // Track event at the start of JSON export $isSubmitting = true; abortController = new AbortController(); // Initialize abort controller exportProgress = 0; // Reset progress @@ -215,6 +214,8 @@ message: `JSON export complete — ${allRows.length} row${allRows.length !== 1 ? 's' : ''} downloaded` }); + trackEvent(Submit.DatabaseExportJson); + await goto(tableUrl); } } catch (error) { @@ -381,4 +382,18 @@ .disabled-checkbox :global(*) { cursor: unset; } + + .cancel-btn { + background: none; + border: 1px solid currentColor; + border-radius: 0.25rem; + padding: 0.125rem 0.5rem; + cursor: pointer; + font-size: 0.75rem; + opacity: 0.8; + } + + .cancel-btn:hover { + opacity: 1; + } From 485804ae0b593a382010ca8296a33d227c15d1ad Mon Sep 17 00:00:00 2001 From: Divyansh2992 Date: Sun, 1 Mar 2026 21:51:14 +0530 Subject: [PATCH 12/16] Document count in notification --- .../table-[table]/export/+page.svelte | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte index 1bd9342410..616429ce46 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte @@ -143,13 +143,7 @@ let lastId: string | undefined = undefined; let fetched = 0; let total = Infinity; - - // Add a warning for potentially large exports - addNotification({ - type: 'info', - message: 'JSON export started. This may take a while for large datasets.', - timeout: 5000 // Keep it visible for a bit - }); + let totalKnown = false; while (fetched < total) { // Check for abort signal @@ -179,6 +173,22 @@ if (response.rows.length === 0) break; + // After first page, we know the real total — notify the user + if (!totalKnown) { + totalKnown = true; + addNotification({ + type: 'info', + message: `Exporting ${total.toLocaleString()} row${total !== 1 ? 's' : ''}…`, + timeout: 5000 + }); + if (total > 10_000) { + addNotification({ + type: 'warning', + message: `Large export (${total.toLocaleString()} rows) — this may use significant browser memory.` + }); + } + } + const filtered = response.rows.map((row) => { const obj: Record = {}; for (const col of selectedCols) { From 0c658f82560da01df60a63696164f69a1e77805c Mon Sep 17 00:00:00 2001 From: Divyansh2992 Date: Fri, 6 Mar 2026 00:28:53 +0530 Subject: [PATCH 13/16] fixed the issue --- .../table-[table]/export/+page.svelte | 55 +++++++++---------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte index 616429ce46..32de5adcbf 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte @@ -126,7 +126,7 @@ } catch (error) { addNotification({ type: 'error', - message: error.message + message: error?.message || String(error) }); trackError(error, Submit.DatabaseExportCsv); @@ -146,14 +146,6 @@ let totalKnown = false; while (fetched < total) { - // Check for abort signal - if (abortController.signal.aborted) { - addNotification({ - type: 'warning', - message: 'JSON export cancelled.' - }); - break; // Exit the loop if aborted - } const pageQueries = [Query.limit(pageSize), ...activeQueries]; @@ -166,7 +158,8 @@ .tablesDB.listRows({ databaseId: page.params.database, tableId: page.params.table, - queries: pageQueries + queries: pageQueries, + signal: abortController.signal }); total = response.total; @@ -229,11 +222,18 @@ await goto(tableUrl); } } catch (error) { - addNotification({ - type: 'error', - message: error.message - }); - trackError(error, Submit.DatabaseExportJson); + if (error?.name === 'AbortError') { + addNotification({ + type: 'warning', + message: 'JSON export cancelled.' + }); + } else { + addNotification({ + type: 'error', + message: error?.message || String(error) + }); + trackError(error, Submit.DatabaseExportJson); + } } finally { $isSubmitting = false; exportProgress = 0; // Reset progress @@ -258,9 +258,16 @@ {#if exportFormat === 'json' && $isSubmitting} -
-
- +
+
+
+
{/if} @@ -393,17 +400,5 @@ cursor: unset; } - .cancel-btn { - background: none; - border: 1px solid currentColor; - border-radius: 0.25rem; - padding: 0.125rem 0.5rem; - cursor: pointer; - font-size: 0.75rem; - opacity: 0.8; - } - .cancel-btn:hover { - opacity: 1; - } From a80dfd27d60a2a553b921e5b691adff8391d5e29 Mon Sep 17 00:00:00 2001 From: Divyansh2992 Date: Fri, 6 Mar 2026 01:00:05 +0530 Subject: [PATCH 14/16] fixing Hardcoded progress bar colors bypass design system and break dark-mode support --- .../table-[table]/export/+page.svelte | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte index 32de5adcbf..eb996b966a 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte @@ -132,9 +132,12 @@ trackError(error, Submit.DatabaseExportCsv); } } else { + // Capture filename at export start — prevents mid-export format changes from + // causing a .csv file name on a JSON download (format selector stays visible). + const capturedFilename = filename; $isSubmitting = true; - abortController = new AbortController(); // Initialize abort controller - exportProgress = 0; // Reset progress + abortController = new AbortController(); + exportProgress = 0; try { const activeQueries = exportWithFilters ? Array.from(localQueries.values()) : []; @@ -202,7 +205,7 @@ const url = URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.href = url; - anchor.download = filename; + anchor.download = capturedFilename; document.body.appendChild(anchor); anchor.click(); @@ -258,14 +261,15 @@ {#if exportFormat === 'json' && $isSubmitting} -
+
+ class="export-progress-bar" + style="--progress: {exportProgress}%;">
@@ -309,6 +313,7 @@ id="exportFormat" label="Format" bind:value={exportFormat} + disabled={$isSubmitting} options={[ { value: 'csv', label: 'CSV' }, { value: 'json', label: 'JSON' } @@ -400,5 +405,21 @@ cursor: unset; } + .progress-container { + margin-top: 1rem; + display: flex; + align-items: center; + gap: 0.5rem; + } + .export-progress-bar { + flex: 1; + height: 0.5rem; + border-radius: 0.25rem; + background: linear-gradient( + to right, + var(--color-success, #4caf50) var(--progress), + var(--color-neutral-80, #e0e0e0) 0% + ); + } From f32a9ea1ca147c2d98d4b3e5c55d21321173292f Mon Sep 17 00:00:00 2001 From: Divyansh2992 Date: Fri, 6 Mar 2026 10:21:47 +0530 Subject: [PATCH 15/16] chore: update sass to fix immutable prototype pollution vulnerability (GHSA-wf6x-7x77-mvgw) --- bun.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bun.lock b/bun.lock index 46d2f28474..f955bd3265 100644 --- a/bun.lock +++ b/bun.lock @@ -61,7 +61,7 @@ "kleur": "^4.1.5", "prettier": "^3.5.3", "prettier-plugin-svelte": "^3.3.3", - "sass": "^1.86.0", + "sass": "^1.97.3", "svelte": "^5.25.3", "svelte-check": "^4.1.5", "svelte-preprocess": "^6.0.3", @@ -926,7 +926,7 @@ "ignore": ["ignore@6.0.2", "", {}, "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A=="], - "immutable": ["immutable@5.1.4", "", {}, "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA=="], + "immutable": ["immutable@5.1.5", "", {}, "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A=="], "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], @@ -1252,7 +1252,7 @@ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], - "sass": ["sass@1.94.2", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A=="], + "sass": ["sass@1.97.3", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg=="], "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="], From d0f256cef74219d6a4d046b67d5601ef128a91b9 Mon Sep 17 00:00:00 2001 From: Divyansh2992 Date: Fri, 6 Mar 2026 17:38:40 +0530 Subject: [PATCH 16/16] Resolved the issue --- package.json | 4 ++-- .../table-[table]/export/+page.svelte | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 02c690cd37..d5b3d90599 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "kleur": "^4.1.5", "prettier": "^3.5.3", "prettier-plugin-svelte": "^3.3.3", - "sass": "^1.86.0", + "sass": "^1.97.3", "svelte": "^5.25.3", "svelte-check": "^4.1.5", "svelte-preprocess": "^6.0.3", @@ -91,4 +91,4 @@ "vite": "npm:rolldown-vite@latest", "minimatch": "10.2.3" } -} +} \ No newline at end of file diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte index eb996b966a..786e709448 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte @@ -149,8 +149,24 @@ let totalKnown = false; while (fetched < total) { + // Explicit guard in case the SDK doesn't throw an AbortError + if (abortController?.signal.aborted) break; + + // Strip any pagination-control queries the user may have added + // (limit, offset, cursorAfter, cursorBefore) to avoid conflicts + // with the paginator's own directives. + const sanitizedQueries = activeQueries.filter((q) => { + try { + const parsed = JSON.parse(q); + return !['limit', 'offset', 'cursorAfter', 'cursorBefore'].includes( + parsed?.method + ); + } catch { + return true; // keep unparseable queries as-is + } + }); - const pageQueries = [Query.limit(pageSize), ...activeQueries]; + const pageQueries = [Query.limit(pageSize), ...sanitizedQueries]; if (lastId) { pageQueries.push(Query.cursorAfter(lastId));