Skip to content

Commit f35f8a9

Browse files
committed
add AI generated code to retrieve some db data
1 parent b8a7d95 commit f35f8a9

File tree

2 files changed

+364
-8
lines changed

2 files changed

+364
-8
lines changed

src/extension.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { getTitleForKey, handleMessage } from './utils'
1111
import { ViewId } from './constants'
1212
import { logger } from './logger'
1313
import { registerUriHandler } from './utils/handleUri'
14+
import * as extensionApi from './extensionApi'
1415

1516
dotenv.config({ path: path.join(__dirname, '..', '.env') })
1617

@@ -220,15 +221,20 @@ export async function activate(context: vscode.ExtensionContext) {
220221

221222
registerUriHandler()
222223

223-
function helloWorld() {
224-
const text = 'hello world'
225-
console.log(text)
226-
227-
return text
228-
}
229-
224+
// Return the extension API to expose functionalities to the outer world
230225
return {
231-
helloWorld,
226+
// Core database operations
227+
getAllDatabases: extensionApi.getAllDatabases,
228+
getDatabaseById: extensionApi.getDatabaseById,
229+
230+
// Index operations
231+
getIndexDefinition: extensionApi.getIndexDefinition,
232+
getAllIndexes: extensionApi.getAllIndexes,
233+
hasRedisSearchModule: extensionApi.hasRedisSearchModule,
234+
235+
// CLI operations
236+
openCliForDatabase: extensionApi.openCliForDatabase,
237+
executeRedisCommand: extensionApi.executeRedisCommand,
232238
}
233239
}
234240

src/extensionApi.ts

Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
import * as vscode from 'vscode'
2+
import axios from 'axios'
3+
import { getUIStorageField, setUIStorageField } from './lib'
4+
5+
/*
6+
* Extension API Usage Examples:
7+
*
8+
* // Get the extension API from the activate return value
9+
* const redisExtension = vscode.extensions.getExtension('Redis.redis-for-vscode')
10+
* const api = redisExtension?.exports
11+
*
12+
* // Example usage:
13+
* // 1. Get all databases
14+
* const databases = await api.getAllDatabases()
15+
* console.log('All databases:', databases)
16+
*
17+
* // 2. Get all indexes for a specific database
18+
* const indexes = await api.getAllIndexes('database-id-here')
19+
* console.log('Indexes:', indexes)
20+
*
21+
* // 3. Get index definition
22+
* const definition = await api.getIndexDefinition('database-id', 'index-name')
23+
* console.log('Index definition:', definition)
24+
*
25+
* // 4. Open CLI for a database
26+
* const success = await api.openCliForDatabase('database-id')
27+
* console.log('CLI opened:', success)
28+
*
29+
* // 5. Execute custom Redis command
30+
* const result = await api.executeRedisCommand('database-id', 'INFO server')
31+
* console.log('Command result:', result)
32+
*/
33+
34+
// Types
35+
export interface DatabaseInstance {
36+
id: string
37+
name: string
38+
host: string
39+
port: number
40+
connectionType?: string
41+
modules?: any[]
42+
}
43+
44+
export interface IndexDefinition {
45+
[key: string]: any
46+
}
47+
48+
export interface CliCommandResponse {
49+
response: any
50+
status: 'success' | 'fail'
51+
}
52+
53+
// Helper function to get backend base URL
54+
async function getBackendBaseUrl(): Promise<string> {
55+
const appPort = await getUIStorageField('appPort') || '5001'
56+
return `http://localhost:${appPort}`
57+
}
58+
59+
// Helper function to execute Redis CLI command on a database
60+
async function executeCliCommand(databaseId: string, command: string): Promise<CliCommandResponse | null> {
61+
try {
62+
const baseUrl = await getBackendBaseUrl()
63+
64+
// First create a CLI client for the database
65+
const createResponse = await axios.post(`${baseUrl}/databases/${databaseId}/cli`)
66+
67+
if (createResponse.status !== 201) {
68+
console.error('Failed to create CLI client')
69+
return null
70+
}
71+
72+
const cliClientUuid = createResponse.data?.uuid
73+
74+
if (!cliClientUuid) {
75+
console.error('No CLI client UUID received')
76+
return null
77+
}
78+
79+
try {
80+
// Execute the command
81+
const commandResponse = await axios.post(
82+
`${baseUrl}/databases/${databaseId}/cli/${cliClientUuid}/send-command`,
83+
{
84+
command,
85+
outputFormat: 'RAW',
86+
},
87+
)
88+
89+
// Clean up: delete the CLI client
90+
await axios.delete(`${baseUrl}/databases/${databaseId}/cli/${cliClientUuid}`)
91+
92+
if (commandResponse.status === 200) {
93+
return {
94+
response: commandResponse.data.response,
95+
status: commandResponse.data.status,
96+
}
97+
}
98+
99+
return null
100+
} catch (error) {
101+
// Clean up: delete the CLI client even if command execution failed
102+
try {
103+
await axios.delete(`${baseUrl}/databases/${databaseId}/cli/${cliClientUuid}`)
104+
} catch (cleanupError) {
105+
console.error('Failed to cleanup CLI client:', cleanupError)
106+
}
107+
throw error
108+
}
109+
} catch (error) {
110+
console.error(`Error executing command '${command}' on database '${databaseId}':`, error)
111+
return null
112+
}
113+
}
114+
115+
/**
116+
* Get all databases from the backend
117+
* @returns Promise<DatabaseInstance[]> Array of all database instances
118+
*/
119+
export async function getAllDatabases(): Promise<DatabaseInstance[]> {
120+
try {
121+
const baseUrl = await getBackendBaseUrl()
122+
const response = await axios.get(`${baseUrl}/databases`)
123+
124+
if (response.status === 200 && Array.isArray(response.data)) {
125+
return response.data.map((db: any) => ({
126+
id: db.id,
127+
name: db.name || `${db.host}:${db.port}`,
128+
host: db.host,
129+
port: db.port,
130+
connectionType: db.connectionType,
131+
modules: db.modules || [],
132+
}))
133+
}
134+
135+
return []
136+
} catch (error) {
137+
console.error('Error getting all databases:', error)
138+
return []
139+
}
140+
}
141+
142+
/**
143+
* Get index definition by database ID and index name
144+
* @param databaseId The database ID
145+
* @param indexName The name of the index
146+
* @returns Promise<IndexDefinition | null> The index definition or null if not found
147+
*/
148+
export async function getIndexDefinition(databaseId: string, indexName: string): Promise<IndexDefinition | null> {
149+
try {
150+
// Get database info first to check if it has Redis Search module
151+
const databases = await getAllDatabases()
152+
const database = databases.find((db) => db.id === databaseId)
153+
154+
if (!database) {
155+
throw new Error(`Database with ID '${databaseId}' not found`)
156+
}
157+
158+
// Check if the database has Redis Search module
159+
const hasSearchModule = database.modules?.some((module) =>
160+
['search', 'searchlight', 'ft', 'ftl'].includes(module.name?.toLowerCase() || ''),
161+
)
162+
163+
if (!hasSearchModule) {
164+
console.warn(`Database '${databaseId}' does not have Redis Search module loaded`)
165+
return null
166+
}
167+
168+
// Execute FT.INFO command to get index definition
169+
const result = await executeCliCommand(databaseId, `FT.INFO ${indexName}`)
170+
171+
if (result && result.status === 'success') {
172+
const { response } = result
173+
174+
if (Array.isArray(response)) {
175+
// FT.INFO returns an array with alternating keys and values
176+
const definition: IndexDefinition = {}
177+
for (let i = 0; i < response.length; i += 2) {
178+
if (i + 1 < response.length) {
179+
const key = String(response[i])
180+
const value = response[i + 1]
181+
definition[key] = value
182+
}
183+
}
184+
return definition
185+
} if (typeof response === 'string') {
186+
// Handle error responses
187+
if (response.includes('Unknown index name')
188+
|| response.includes('no such index')
189+
|| response.includes('ERR no such index')) {
190+
return null
191+
}
192+
193+
// Try to parse as JSON in case of different format
194+
try {
195+
return JSON.parse(response)
196+
} catch {
197+
// Return as raw string if can't parse
198+
return { raw: response }
199+
}
200+
}
201+
}
202+
203+
return null
204+
} catch (error) {
205+
console.error(`Error getting index definition for '${indexName}' in database '${databaseId}':`, error)
206+
return null
207+
}
208+
}
209+
210+
/**
211+
* Get all indexes for a specific database
212+
* @param databaseId The database ID
213+
* @returns Promise<string[]> Array of index names
214+
*/
215+
export async function getAllIndexes(databaseId: string): Promise<string[]> {
216+
try {
217+
// Get database info first to check if it has Redis Search module
218+
const databases = await getAllDatabases()
219+
const database = databases.find((db) => db.id === databaseId)
220+
221+
if (!database) {
222+
throw new Error(`Database with ID '${databaseId}' not found`)
223+
}
224+
225+
// Check if the database has Redis Search module
226+
const hasSearchModule = database.modules?.some((module) =>
227+
['search', 'searchlight', 'ft', 'ftl'].includes(module.name?.toLowerCase() || ''),
228+
)
229+
230+
if (!hasSearchModule) {
231+
console.warn(`Database '${databaseId}' does not have Redis Search module loaded`)
232+
return []
233+
}
234+
235+
// Execute FT._LIST command to get all indexes
236+
const result = await executeCliCommand(databaseId, 'FT._LIST')
237+
238+
if (result && result.status === 'success') {
239+
const { response } = result
240+
241+
if (Array.isArray(response)) {
242+
return response.map((index) => String(index))
243+
} if (typeof response === 'string') {
244+
// Handle string response - might be space/newline separated or JSON
245+
try {
246+
const parsed = JSON.parse(response)
247+
if (Array.isArray(parsed)) {
248+
return parsed.map((index) => String(index))
249+
}
250+
} catch {
251+
// If not JSON, might be space/newline separated
252+
const trimmed = response.trim()
253+
if (trimmed === '') {
254+
return []
255+
}
256+
return trimmed.split(/\s+/).filter((name) => name.length > 0)
257+
}
258+
}
259+
}
260+
261+
return []
262+
} catch (error) {
263+
console.error(`Error getting indexes for database '${databaseId}':`, error)
264+
return []
265+
}
266+
}
267+
268+
/**
269+
* Open CLI for a specific database
270+
* @param databaseId The database ID to open CLI for
271+
* @returns Promise<boolean> True if CLI was opened successfully
272+
*/
273+
export async function openCliForDatabase(databaseId: string): Promise<boolean> {
274+
try {
275+
// Get database info first
276+
const databases = await getAllDatabases()
277+
const database = databases.find((db) => db.id === databaseId)
278+
279+
if (!database) {
280+
throw new Error(`Database with ID '${databaseId}' not found`)
281+
}
282+
283+
// Set the database in UI storage
284+
await setUIStorageField('database', database)
285+
286+
// Execute the addCli command
287+
await vscode.commands.executeCommand('RedisForVSCode.addCli', {
288+
data: { database },
289+
})
290+
291+
return true
292+
} catch (error) {
293+
console.error(`Error opening CLI for database '${databaseId}':`, error)
294+
return false
295+
}
296+
}
297+
298+
/**
299+
* Execute a custom Redis command on a database
300+
* @param databaseId The database ID
301+
* @param command The Redis command to execute
302+
* @returns Promise<any> The command response
303+
*/
304+
export async function executeRedisCommand(databaseId: string, command: string): Promise<any> {
305+
try {
306+
const result = await executeCliCommand(databaseId, command)
307+
return result?.response || null
308+
} catch (error) {
309+
console.error(`Error executing command '${command}' on database '${databaseId}':`, error)
310+
return null
311+
}
312+
}
313+
314+
/**
315+
* Check if a database has Redis Search module
316+
* @param databaseId The database ID
317+
* @returns Promise<boolean> True if the database has Redis Search module
318+
*/
319+
export async function hasRedisSearchModule(databaseId: string): Promise<boolean> {
320+
try {
321+
const databases = await getAllDatabases()
322+
const database = databases.find((db) => db.id === databaseId)
323+
324+
if (!database) {
325+
return false
326+
}
327+
328+
return database.modules?.some((module) =>
329+
['search', 'searchlight', 'ft', 'ftl'].includes(module.name?.toLowerCase() || ''),
330+
) || false
331+
} catch (error) {
332+
console.error(`Error checking Redis Search module for database '${databaseId}':`, error)
333+
return false
334+
}
335+
}
336+
337+
/**
338+
* Get database by ID
339+
* @param databaseId The database ID
340+
* @returns Promise<DatabaseInstance | null> The database instance or null if not found
341+
*/
342+
export async function getDatabaseById(databaseId: string): Promise<DatabaseInstance | null> {
343+
try {
344+
const databases = await getAllDatabases()
345+
return databases.find((db) => db.id === databaseId) || null
346+
} catch (error) {
347+
console.error(`Error getting database by ID '${databaseId}':`, error)
348+
return null
349+
}
350+
}

0 commit comments

Comments
 (0)