diff --git a/README.md b/README.md index 86b3745..bb1bc8e 100644 --- a/README.md +++ b/README.md @@ -222,12 +222,32 @@ You can request a `database_dump.sql` file that exports your database schema and
 
-curl --location 'https://starbasedb.YOUR-ID-HERE.workers.dev/dump' \
+curl --location 'https://starbasedb.YOUR-ID-HERE.workers.dev/export/dump' \
 --header 'Authorization: Bearer ABC123' 
 --output database_dump.sql
 
 
+

JSON Data Export

+
+
+curl
+--location 'https://starbasedb.YOUR-ID-HERE.workers.dev/export/json/users' \
+--header 'Authorization: Bearer ABC123'
+--output output.json
+
+
+ +

CSV Data Export

+
+
+curl
+--location 'https://starbasedb.YOUR-ID-HERE.workers.dev/export/csv/users' \
+--header 'Authorization: Bearer ABC123'
+--output output.csv
+
+
+

Contributing

We welcome contributions! Please refer to our Contribution Guide for more details.

diff --git a/src/export/csv.ts b/src/export/csv.ts new file mode 100644 index 0000000..c708f2e --- /dev/null +++ b/src/export/csv.ts @@ -0,0 +1,40 @@ +import { getTableData, createExportResponse } from './index'; +import { createResponse } from '../utils'; + +export async function exportTableToCsvRoute( + sql: any, + operationQueue: any, + ctx: any, + processingOperation: { value: boolean }, + tableName: string +): Promise { + try { + const data = await getTableData(sql, operationQueue, ctx, processingOperation, tableName); + + if (data === null) { + return createResponse(undefined, `Table '${tableName}' does not exist.`, 404); + } + + // Convert the result to CSV + let csvContent = ''; + if (data.length > 0) { + // Add headers + csvContent += Object.keys(data[0]).join(',') + '\n'; + + // Add data rows + data.forEach((row: any) => { + csvContent += Object.values(row).map(value => { + if (typeof value === 'string' && value.includes(',')) { + return `"${value.replace(/"/g, '""')}"`; + } + return value; + }).join(',') + '\n'; + }); + } + + return createExportResponse(csvContent, `${tableName}_export.csv`, 'text/csv'); + } catch (error: any) { + console.error('CSV Export Error:', error); + return createResponse(undefined, 'Failed to export table to CSV', 500); + } +} diff --git a/src/export/index.ts b/src/export/index.ts new file mode 100644 index 0000000..60066b3 --- /dev/null +++ b/src/export/index.ts @@ -0,0 +1,50 @@ +import { enqueueOperation, processNextOperation } from '../operation'; + +export async function getTableData( + sql: any, + operationQueue: any, + ctx: any, + processingOperation: { value: boolean }, + tableName: string +): Promise { + try { + // Verify if the table exists + const tableExistsResult = await enqueueOperation( + [{ sql: `SELECT name FROM sqlite_master WHERE type='table' AND name=?;`, params: [tableName] }], + false, + false, + operationQueue, + () => processNextOperation(sql, operationQueue, ctx, processingOperation) + ); + + if (tableExistsResult.result.length === 0) { + return null; + } + + // Get table data + const dataResult = await enqueueOperation( + [{ sql: `SELECT * FROM ${tableName};` }], + false, + false, + operationQueue, + () => processNextOperation(sql, operationQueue, ctx, processingOperation) + ); + + return dataResult.result; + } catch (error: any) { + console.error('Table Data Fetch Error:', error); + throw error; + } +} + +export function createExportResponse(data: any, fileName: string, contentType: string): Response { + const blob = new Blob([data], { type: contentType }); + + const headers = new Headers({ + 'Content-Type': contentType, + 'Content-Disposition': `attachment; filename="${fileName}"`, + }); + + return new Response(blob, { headers }); +} + diff --git a/src/export/json.ts b/src/export/json.ts new file mode 100644 index 0000000..d2ac413 --- /dev/null +++ b/src/export/json.ts @@ -0,0 +1,26 @@ +import { getTableData, createExportResponse } from './index'; +import { createResponse } from '../utils'; + +export async function exportTableToJsonRoute( + sql: any, + operationQueue: any, + ctx: any, + processingOperation: { value: boolean }, + tableName: string +): Promise { + try { + const data = await getTableData(sql, operationQueue, ctx, processingOperation, tableName); + + if (data === null) { + return createResponse(undefined, `Table '${tableName}' does not exist.`, 404); + } + + // Convert the result to JSON + const jsonData = JSON.stringify(data, null, 4); + + return createExportResponse(jsonData, `${tableName}_export.json`, 'application/json'); + } catch (error: any) { + console.error('JSON Export Error:', error); + return createResponse(undefined, 'Failed to export table to JSON', 500); + } +} diff --git a/src/index.ts b/src/index.ts index a614c4f..5dbc78c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,8 @@ import { enqueueOperation, OperationQueueItem, processNextOperation } from './op import { LiteREST } from './literest'; import handleStudioRequest from "./studio"; import { dumpDatabaseRoute } from './export/dump'; +import { exportTableToJsonRoute } from './export/json'; +import { exportTableToCsvRoute } from './export/csv'; const DURABLE_OBJECT_ID = 'sql-durable-object'; @@ -125,8 +127,20 @@ export class DatabaseDurableObject extends DurableObject { return this.statusRoute(request); } else if (url.pathname.startsWith('/rest')) { return await this.liteREST.handleRequest(request); - } else if (request.method === 'GET' && url.pathname === '/dump') { + } else if (request.method === 'GET' && url.pathname === '/export/dump') { return dumpDatabaseRoute(this.sql, this.operationQueue, this.ctx, this.processingOperation); + } else if (request.method === 'GET' && url.pathname.startsWith('/export/json/')) { + const tableName = url.pathname.split('/').pop(); + if (!tableName) { + return createResponse(undefined, 'Table name is required', 400); + } + return exportTableToJsonRoute(this.sql, this.operationQueue, this.ctx, this.processingOperation, tableName); + } else if (request.method === 'GET' && url.pathname.startsWith('/export/csv/')) { + const tableName = url.pathname.split('/').pop(); + if (!tableName) { + return createResponse(undefined, 'Table name is required', 400); + } + return exportTableToCsvRoute(this.sql, this.operationQueue, this.ctx, this.processingOperation, tableName); } else { return createResponse(undefined, 'Unknown operation', 400); } @@ -232,4 +246,4 @@ export default { */ return await stub.fetch(request); }, -} satisfies ExportedHandler; \ No newline at end of file +} satisfies ExportedHandler;