apigraveyard 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +28 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
- package/.github/ROADMAP_ISSUES.md +169 -0
- package/LICENSE +21 -0
- package/README.md +501 -0
- package/bin/apigraveyard.js +686 -0
- package/hooks/pre-commit +203 -0
- package/package.json +34 -0
- package/scripts/install-hooks.js +182 -0
- package/src/database.js +518 -0
- package/src/display.js +534 -0
- package/src/scanner.js +294 -0
- package/src/tester.js +578 -0
package/src/database.js
ADDED
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Module
|
|
3
|
+
* JSON-based storage system for API key tracking
|
|
4
|
+
* Stores data in ~/.apigraveyard.json
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs/promises';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import os from 'os';
|
|
10
|
+
import crypto from 'crypto';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Database file path in user's home directory
|
|
14
|
+
* @constant {string}
|
|
15
|
+
*/
|
|
16
|
+
const DB_FILE = path.join(os.homedir(), '.apigraveyard.json');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Backup file path
|
|
20
|
+
* @constant {string}
|
|
21
|
+
*/
|
|
22
|
+
const DB_BACKUP_FILE = path.join(os.homedir(), '.apigraveyard.backup.json');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Current database schema version
|
|
26
|
+
* @constant {string}
|
|
27
|
+
*/
|
|
28
|
+
const DB_VERSION = '1.0.0';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Creates an empty database structure
|
|
32
|
+
*
|
|
33
|
+
* @returns {Object} - Empty database object with default structure
|
|
34
|
+
*/
|
|
35
|
+
function createEmptyDatabase() {
|
|
36
|
+
return {
|
|
37
|
+
version: DB_VERSION,
|
|
38
|
+
createdAt: new Date().toISOString(),
|
|
39
|
+
updatedAt: new Date().toISOString(),
|
|
40
|
+
projects: [],
|
|
41
|
+
bannedKeys: []
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Generates a UUID v4 for project identification
|
|
47
|
+
*
|
|
48
|
+
* @returns {string} - UUID string
|
|
49
|
+
*/
|
|
50
|
+
function generateUUID() {
|
|
51
|
+
return crypto.randomUUID();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Normalizes a file path for consistent comparison
|
|
56
|
+
*
|
|
57
|
+
* @param {string} filePath - Path to normalize
|
|
58
|
+
* @returns {string} - Normalized path
|
|
59
|
+
*/
|
|
60
|
+
function normalizePath(filePath) {
|
|
61
|
+
return path.resolve(filePath).toLowerCase();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Reads and parses the database file
|
|
66
|
+
*
|
|
67
|
+
* @returns {Promise<Object>} - Parsed database object
|
|
68
|
+
* @throws {Error} - If file cannot be read or parsed
|
|
69
|
+
*/
|
|
70
|
+
async function readDatabase() {
|
|
71
|
+
try {
|
|
72
|
+
const data = await fs.readFile(DB_FILE, 'utf-8');
|
|
73
|
+
return JSON.parse(data);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (error.code === 'ENOENT') {
|
|
76
|
+
// File doesn't exist, return empty database
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
if (error instanceof SyntaxError) {
|
|
80
|
+
// JSON parse error - try to recover from backup
|
|
81
|
+
console.warn('⚠️ Database file corrupted, attempting to restore from backup...');
|
|
82
|
+
try {
|
|
83
|
+
const backupData = await fs.readFile(DB_BACKUP_FILE, 'utf-8');
|
|
84
|
+
return JSON.parse(backupData);
|
|
85
|
+
} catch {
|
|
86
|
+
console.error('❌ Backup also corrupted or missing. Creating new database.');
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Writes the database to file atomically
|
|
96
|
+
* Uses write-to-temp-then-rename strategy for safety
|
|
97
|
+
*
|
|
98
|
+
* @param {Object} db - Database object to write
|
|
99
|
+
* @returns {Promise<void>}
|
|
100
|
+
* @throws {Error} - If file cannot be written
|
|
101
|
+
*/
|
|
102
|
+
async function writeDatabase(db) {
|
|
103
|
+
// Update timestamp
|
|
104
|
+
db.updatedAt = new Date().toISOString();
|
|
105
|
+
|
|
106
|
+
const tempFile = `${DB_FILE}.tmp`;
|
|
107
|
+
const jsonData = JSON.stringify(db, null, 2);
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
// Backup existing database before writing
|
|
111
|
+
try {
|
|
112
|
+
await fs.access(DB_FILE);
|
|
113
|
+
await fs.copyFile(DB_FILE, DB_BACKUP_FILE);
|
|
114
|
+
} catch {
|
|
115
|
+
// No existing file to backup, that's OK
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Write to temp file first
|
|
119
|
+
await fs.writeFile(tempFile, jsonData, 'utf-8');
|
|
120
|
+
|
|
121
|
+
// Rename temp file to actual file (atomic operation)
|
|
122
|
+
await fs.rename(tempFile, DB_FILE);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
// Clean up temp file if it exists
|
|
125
|
+
try {
|
|
126
|
+
await fs.unlink(tempFile);
|
|
127
|
+
} catch {
|
|
128
|
+
// Ignore cleanup errors
|
|
129
|
+
}
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Initializes the database
|
|
136
|
+
* Creates the database file if it doesn't exist
|
|
137
|
+
*
|
|
138
|
+
* @returns {Promise<Object>} - The database object
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* const db = await initDatabase();
|
|
142
|
+
* console.log(`Database version: ${db.version}`);
|
|
143
|
+
*/
|
|
144
|
+
export async function initDatabase() {
|
|
145
|
+
let db = await readDatabase();
|
|
146
|
+
|
|
147
|
+
if (!db) {
|
|
148
|
+
db = createEmptyDatabase();
|
|
149
|
+
await writeDatabase(db);
|
|
150
|
+
console.log('✓ Created new APIgraveyard database at', DB_FILE);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Handle version migrations if needed
|
|
154
|
+
if (db.version !== DB_VERSION) {
|
|
155
|
+
console.log(`📦 Migrating database from v${db.version} to v${DB_VERSION}`);
|
|
156
|
+
db.version = DB_VERSION;
|
|
157
|
+
await writeDatabase(db);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return db;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Saves a project with scan results to the database
|
|
165
|
+
* Updates existing project if path already exists
|
|
166
|
+
*
|
|
167
|
+
* @param {string} projectPath - Full path to the project directory
|
|
168
|
+
* @param {Object} scanResults - Results from scanner.scanDirectory()
|
|
169
|
+
* @param {number} scanResults.totalFiles - Number of files scanned
|
|
170
|
+
* @param {Array} scanResults.keysFound - Array of found keys
|
|
171
|
+
* @returns {Promise<Object>} - The saved project object
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* const results = await scanDirectory('./myproject');
|
|
175
|
+
* const project = await saveProject('./myproject', results);
|
|
176
|
+
* console.log(`Saved project: ${project.id}`);
|
|
177
|
+
*/
|
|
178
|
+
export async function saveProject(projectPath, scanResults) {
|
|
179
|
+
const db = await initDatabase();
|
|
180
|
+
const normalizedPath = normalizePath(projectPath);
|
|
181
|
+
const projectName = path.basename(projectPath);
|
|
182
|
+
|
|
183
|
+
// Check if project already exists
|
|
184
|
+
const existingIndex = db.projects.findIndex(
|
|
185
|
+
p => normalizePath(p.path) === normalizedPath
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const project = {
|
|
189
|
+
id: existingIndex >= 0 ? db.projects[existingIndex].id : generateUUID(),
|
|
190
|
+
name: projectName,
|
|
191
|
+
path: path.resolve(projectPath),
|
|
192
|
+
scannedAt: new Date().toISOString(),
|
|
193
|
+
totalFiles: scanResults.totalFiles,
|
|
194
|
+
keys: scanResults.keysFound.map(key => ({
|
|
195
|
+
service: key.service,
|
|
196
|
+
key: key.key,
|
|
197
|
+
fullKey: key.fullKey,
|
|
198
|
+
status: null,
|
|
199
|
+
filePath: key.filePath,
|
|
200
|
+
lineNumber: key.lineNumber,
|
|
201
|
+
column: key.column,
|
|
202
|
+
lastTested: null,
|
|
203
|
+
quotaInfo: {}
|
|
204
|
+
}))
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
if (existingIndex >= 0) {
|
|
208
|
+
// Update existing project
|
|
209
|
+
db.projects[existingIndex] = project;
|
|
210
|
+
} else {
|
|
211
|
+
// Add new project
|
|
212
|
+
db.projects.push(project);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
await writeDatabase(db);
|
|
216
|
+
return project;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Retrieves a project by its path
|
|
221
|
+
*
|
|
222
|
+
* @param {string} projectPath - Path to the project
|
|
223
|
+
* @returns {Promise<Object|null>} - Project object or null if not found
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* const project = await getProject('./myproject');
|
|
227
|
+
* if (project) {
|
|
228
|
+
* console.log(`Found ${project.keys.length} keys`);
|
|
229
|
+
* }
|
|
230
|
+
*/
|
|
231
|
+
export async function getProject(projectPath) {
|
|
232
|
+
const db = await initDatabase();
|
|
233
|
+
const normalizedPath = normalizePath(projectPath);
|
|
234
|
+
|
|
235
|
+
const project = db.projects.find(
|
|
236
|
+
p => normalizePath(p.path) === normalizedPath
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
return project || null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Retrieves all projects from the database
|
|
244
|
+
*
|
|
245
|
+
* @returns {Promise<Array>} - Array of all project objects
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* const projects = await getAllProjects();
|
|
249
|
+
* projects.forEach(p => console.log(p.name));
|
|
250
|
+
*/
|
|
251
|
+
export async function getAllProjects() {
|
|
252
|
+
const db = await initDatabase();
|
|
253
|
+
return db.projects;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Updates the status of a specific key in a project
|
|
258
|
+
*
|
|
259
|
+
* @param {string} projectPath - Path to the project
|
|
260
|
+
* @param {string} keyValue - The full key value to update
|
|
261
|
+
* @param {Object} testResult - Test result from tester.js
|
|
262
|
+
* @param {string} testResult.status - Key status (VALID, INVALID, etc.)
|
|
263
|
+
* @param {Object} testResult.details - Additional details from testing
|
|
264
|
+
* @returns {Promise<boolean>} - True if updated, false if not found
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* await updateKeyStatus('./myproject', 'sk-xxx...', {
|
|
268
|
+
* status: 'VALID',
|
|
269
|
+
* details: { modelsCount: 15 }
|
|
270
|
+
* });
|
|
271
|
+
*/
|
|
272
|
+
export async function updateKeyStatus(projectPath, keyValue, testResult) {
|
|
273
|
+
const db = await initDatabase();
|
|
274
|
+
const normalizedPath = normalizePath(projectPath);
|
|
275
|
+
|
|
276
|
+
const project = db.projects.find(
|
|
277
|
+
p => normalizePath(p.path) === normalizedPath
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
if (!project) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const key = project.keys.find(k => k.fullKey === keyValue);
|
|
285
|
+
|
|
286
|
+
if (!key) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
key.status = testResult.status;
|
|
291
|
+
key.lastTested = new Date().toISOString();
|
|
292
|
+
key.quotaInfo = testResult.details || {};
|
|
293
|
+
|
|
294
|
+
if (testResult.error) {
|
|
295
|
+
key.lastError = testResult.error;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
await writeDatabase(db);
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Updates multiple keys in a project with test results
|
|
304
|
+
*
|
|
305
|
+
* @param {string} projectPath - Path to the project
|
|
306
|
+
* @param {Array} testResults - Array of test results from testKeys()
|
|
307
|
+
* @returns {Promise<number>} - Number of keys updated
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* const results = await testKeys(keysFound);
|
|
311
|
+
* const count = await updateKeysStatus('./myproject', results);
|
|
312
|
+
* console.log(`Updated ${count} keys`);
|
|
313
|
+
*/
|
|
314
|
+
export async function updateKeysStatus(projectPath, testResults) {
|
|
315
|
+
const db = await initDatabase();
|
|
316
|
+
const normalizedPath = normalizePath(projectPath);
|
|
317
|
+
|
|
318
|
+
const project = db.projects.find(
|
|
319
|
+
p => normalizePath(p.path) === normalizedPath
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
if (!project) {
|
|
323
|
+
return 0;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
let updatedCount = 0;
|
|
327
|
+
|
|
328
|
+
for (const result of testResults) {
|
|
329
|
+
const key = project.keys.find(k => k.fullKey === result.fullKey);
|
|
330
|
+
if (key) {
|
|
331
|
+
key.status = result.status;
|
|
332
|
+
key.lastTested = new Date().toISOString();
|
|
333
|
+
key.quotaInfo = result.details || {};
|
|
334
|
+
if (result.error) {
|
|
335
|
+
key.lastError = result.error;
|
|
336
|
+
}
|
|
337
|
+
updatedCount++;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
await writeDatabase(db);
|
|
342
|
+
return updatedCount;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Deletes a project from the database
|
|
347
|
+
*
|
|
348
|
+
* @param {string} projectPath - Path to the project to delete
|
|
349
|
+
* @returns {Promise<boolean>} - True if deleted, false if not found
|
|
350
|
+
*
|
|
351
|
+
* @example
|
|
352
|
+
* const deleted = await deleteProject('./myproject');
|
|
353
|
+
* if (deleted) {
|
|
354
|
+
* console.log('Project removed from tracking');
|
|
355
|
+
* }
|
|
356
|
+
*/
|
|
357
|
+
export async function deleteProject(projectPath) {
|
|
358
|
+
const db = await initDatabase();
|
|
359
|
+
const normalizedPath = normalizePath(projectPath);
|
|
360
|
+
|
|
361
|
+
const initialLength = db.projects.length;
|
|
362
|
+
db.projects = db.projects.filter(
|
|
363
|
+
p => normalizePath(p.path) !== normalizedPath
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
if (db.projects.length < initialLength) {
|
|
367
|
+
await writeDatabase(db);
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Adds a key to the banned keys list
|
|
376
|
+
* Banned keys will be flagged in future scans
|
|
377
|
+
*
|
|
378
|
+
* @param {string} keyValue - The full key value to ban
|
|
379
|
+
* @returns {Promise<boolean>} - True if added, false if already banned
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* await addBannedKey('sk-compromised-key-here');
|
|
383
|
+
*/
|
|
384
|
+
export async function addBannedKey(keyValue) {
|
|
385
|
+
const db = await initDatabase();
|
|
386
|
+
|
|
387
|
+
// Check if already banned
|
|
388
|
+
if (db.bannedKeys.includes(keyValue)) {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
db.bannedKeys.push(keyValue);
|
|
393
|
+
await writeDatabase(db);
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Removes a key from the banned keys list
|
|
399
|
+
*
|
|
400
|
+
* @param {string} keyValue - The full key value to unban
|
|
401
|
+
* @returns {Promise<boolean>} - True if removed, false if not found
|
|
402
|
+
*/
|
|
403
|
+
export async function removeBannedKey(keyValue) {
|
|
404
|
+
const db = await initDatabase();
|
|
405
|
+
|
|
406
|
+
const initialLength = db.bannedKeys.length;
|
|
407
|
+
db.bannedKeys = db.bannedKeys.filter(k => k !== keyValue);
|
|
408
|
+
|
|
409
|
+
if (db.bannedKeys.length < initialLength) {
|
|
410
|
+
await writeDatabase(db);
|
|
411
|
+
return true;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Checks if a key is in the banned keys list
|
|
419
|
+
*
|
|
420
|
+
* @param {string} keyValue - The full key value to check
|
|
421
|
+
* @returns {Promise<boolean>} - True if banned, false otherwise
|
|
422
|
+
*
|
|
423
|
+
* @example
|
|
424
|
+
* if (await isBanned(key.fullKey)) {
|
|
425
|
+
* console.log('⚠️ This key has been marked as compromised!');
|
|
426
|
+
* }
|
|
427
|
+
*/
|
|
428
|
+
export async function isBanned(keyValue) {
|
|
429
|
+
const db = await initDatabase();
|
|
430
|
+
return db.bannedKeys.includes(keyValue);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Gets all banned keys
|
|
435
|
+
*
|
|
436
|
+
* @returns {Promise<string[]>} - Array of banned key values
|
|
437
|
+
*/
|
|
438
|
+
export async function getBannedKeys() {
|
|
439
|
+
const db = await initDatabase();
|
|
440
|
+
return db.bannedKeys;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Gets database statistics
|
|
445
|
+
*
|
|
446
|
+
* @returns {Promise<Object>} - Statistics about the database
|
|
447
|
+
*
|
|
448
|
+
* @example
|
|
449
|
+
* const stats = await getDatabaseStats();
|
|
450
|
+
* console.log(`Tracking ${stats.totalProjects} projects with ${stats.totalKeys} keys`);
|
|
451
|
+
*/
|
|
452
|
+
export async function getDatabaseStats() {
|
|
453
|
+
const db = await initDatabase();
|
|
454
|
+
|
|
455
|
+
const totalKeys = db.projects.reduce((sum, p) => sum + p.keys.length, 0);
|
|
456
|
+
const validKeys = db.projects.reduce(
|
|
457
|
+
(sum, p) => sum + p.keys.filter(k => k.status === 'VALID').length,
|
|
458
|
+
0
|
|
459
|
+
);
|
|
460
|
+
const invalidKeys = db.projects.reduce(
|
|
461
|
+
(sum, p) => sum + p.keys.filter(k => k.status === 'INVALID').length,
|
|
462
|
+
0
|
|
463
|
+
);
|
|
464
|
+
const untestedKeys = db.projects.reduce(
|
|
465
|
+
(sum, p) => sum + p.keys.filter(k => k.status === null).length,
|
|
466
|
+
0
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
return {
|
|
470
|
+
version: db.version,
|
|
471
|
+
totalProjects: db.projects.length,
|
|
472
|
+
totalKeys,
|
|
473
|
+
validKeys,
|
|
474
|
+
invalidKeys,
|
|
475
|
+
untestedKeys,
|
|
476
|
+
bannedKeys: db.bannedKeys.length,
|
|
477
|
+
createdAt: db.createdAt,
|
|
478
|
+
updatedAt: db.updatedAt,
|
|
479
|
+
dbPath: DB_FILE
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Gets the database file path
|
|
485
|
+
*
|
|
486
|
+
* @returns {string} - Path to the database file
|
|
487
|
+
*/
|
|
488
|
+
export function getDatabasePath() {
|
|
489
|
+
return DB_FILE;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Clears all data from the database
|
|
494
|
+
* Creates a fresh empty database
|
|
495
|
+
*
|
|
496
|
+
* @returns {Promise<void>}
|
|
497
|
+
*/
|
|
498
|
+
export async function clearDatabase() {
|
|
499
|
+
const db = createEmptyDatabase();
|
|
500
|
+
await writeDatabase(db);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export default {
|
|
504
|
+
initDatabase,
|
|
505
|
+
saveProject,
|
|
506
|
+
getProject,
|
|
507
|
+
getAllProjects,
|
|
508
|
+
updateKeyStatus,
|
|
509
|
+
updateKeysStatus,
|
|
510
|
+
deleteProject,
|
|
511
|
+
addBannedKey,
|
|
512
|
+
removeBannedKey,
|
|
513
|
+
isBanned,
|
|
514
|
+
getBannedKeys,
|
|
515
|
+
getDatabaseStats,
|
|
516
|
+
getDatabasePath,
|
|
517
|
+
clearDatabase
|
|
518
|
+
};
|