feat: integrate Supabase for database operations and migrations
Add support for Supabase database operations, including migrations and queries. Implement new Supabase-related types, actions, and components to handle database interactions. Enhance the prompt system to include Supabase-specific instructions and constraints. Ensure data integrity and security by enforcing row-level security and proper migration practices.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import type { WebContainer } from '@webcontainer/api';
|
||||
import { path as nodePath } from '~/utils/path';
|
||||
import { atom, map, type MapStore } from 'nanostores';
|
||||
import type { ActionAlert, BoltAction, FileHistory } from '~/types/actions';
|
||||
import type { ActionAlert, BoltAction, FileHistory, SupabaseAction, SupabaseAlert } from '~/types/actions';
|
||||
import { createScopedLogger } from '~/utils/logger';
|
||||
import { unreachable } from '~/utils/unreachable';
|
||||
import type { ActionCallbackData } from './message-parser';
|
||||
@@ -70,16 +70,19 @@ export class ActionRunner {
|
||||
runnerId = atom<string>(`${Date.now()}`);
|
||||
actions: ActionsMap = map({});
|
||||
onAlert?: (alert: ActionAlert) => void;
|
||||
onSupabaseAlert?: (alert: SupabaseAlert) => void;
|
||||
buildOutput?: { path: string; exitCode: number; output: string };
|
||||
|
||||
constructor(
|
||||
webcontainerPromise: Promise<WebContainer>,
|
||||
getShellTerminal: () => BoltShell,
|
||||
onAlert?: (alert: ActionAlert) => void,
|
||||
onSupabaseAlert?: (alert: SupabaseAlert) => void,
|
||||
) {
|
||||
this.#webcontainer = webcontainerPromise;
|
||||
this.#shellTerminal = getShellTerminal;
|
||||
this.onAlert = onAlert;
|
||||
this.onSupabaseAlert = onSupabaseAlert;
|
||||
}
|
||||
|
||||
addAction(data: ActionCallbackData) {
|
||||
@@ -157,6 +160,21 @@ export class ActionRunner {
|
||||
await this.#runFileAction(action);
|
||||
break;
|
||||
}
|
||||
case 'supabase': {
|
||||
try {
|
||||
await this.handleSupabaseAction(action as SupabaseAction);
|
||||
} catch (error: any) {
|
||||
// Update action status
|
||||
this.#updateAction(actionId, {
|
||||
status: 'failed',
|
||||
error: error instanceof Error ? error.message : 'Supabase action failed',
|
||||
});
|
||||
|
||||
// Return early without re-throwing
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'build': {
|
||||
const buildOutput = await this.#runBuildAction(action);
|
||||
|
||||
@@ -377,4 +395,50 @@ export class ActionRunner {
|
||||
output,
|
||||
};
|
||||
}
|
||||
async handleSupabaseAction(action: SupabaseAction) {
|
||||
const { operation, content, filePath } = action;
|
||||
logger.debug('[Supabase Action]:', { operation, filePath, content });
|
||||
|
||||
switch (operation) {
|
||||
case 'migration':
|
||||
if (!filePath) {
|
||||
throw new Error('Migration requires a filePath');
|
||||
}
|
||||
|
||||
// Show alert for migration action
|
||||
this.onSupabaseAlert?.({
|
||||
type: 'info',
|
||||
title: 'Supabase Migration',
|
||||
description: `Create migration file: ${filePath}`,
|
||||
content,
|
||||
source: 'supabase',
|
||||
});
|
||||
|
||||
// Only create the migration file
|
||||
await this.#runFileAction({
|
||||
type: 'file',
|
||||
filePath,
|
||||
content,
|
||||
changeSource: 'supabase',
|
||||
} as any);
|
||||
return { success: true };
|
||||
|
||||
case 'query': {
|
||||
// Always show the alert and let the SupabaseAlert component handle connection state
|
||||
this.onSupabaseAlert?.({
|
||||
type: 'info',
|
||||
title: 'Supabase Query',
|
||||
description: 'Execute database query',
|
||||
content,
|
||||
source: 'supabase',
|
||||
});
|
||||
|
||||
// The actual execution will be triggered from SupabaseChatAlert
|
||||
return { pending: true };
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown operation: ${operation}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ActionType, BoltAction, BoltActionData, FileAction, ShellAction } from '~/types/actions';
|
||||
import type { ActionType, BoltAction, BoltActionData, FileAction, ShellAction, SupabaseAction } from '~/types/actions';
|
||||
import type { BoltArtifactData } from '~/types/artifact';
|
||||
import { createScopedLogger } from '~/utils/logger';
|
||||
import { unreachable } from '~/utils/unreachable';
|
||||
@@ -293,7 +293,27 @@ export class StreamingMessageParser {
|
||||
content: '',
|
||||
};
|
||||
|
||||
if (actionType === 'file') {
|
||||
if (actionType === 'supabase') {
|
||||
const operation = this.#extractAttribute(actionTag, 'operation');
|
||||
|
||||
if (!operation || !['migration', 'query'].includes(operation)) {
|
||||
logger.warn(`Invalid or missing operation for Supabase action: ${operation}`);
|
||||
throw new Error(`Invalid Supabase operation: ${operation}`);
|
||||
}
|
||||
|
||||
(actionAttributes as SupabaseAction).operation = operation as 'migration' | 'query';
|
||||
|
||||
if (operation === 'migration') {
|
||||
const filePath = this.#extractAttribute(actionTag, 'filePath');
|
||||
|
||||
if (!filePath) {
|
||||
logger.warn('Migration requires a filePath');
|
||||
throw new Error('Migration requires a filePath');
|
||||
}
|
||||
|
||||
(actionAttributes as SupabaseAction).filePath = filePath;
|
||||
}
|
||||
} else if (actionType === 'file') {
|
||||
const filePath = this.#extractAttribute(actionTag, 'filePath') as string;
|
||||
|
||||
if (!filePath) {
|
||||
|
||||
Reference in New Issue
Block a user