@nodatachat/guard 2.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.
Files changed (39) hide show
  1. package/LICENSE.md +28 -0
  2. package/README.md +120 -0
  3. package/dist/activation.d.ts +8 -0
  4. package/dist/activation.js +110 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +458 -0
  7. package/dist/code-scanner.d.ts +14 -0
  8. package/dist/code-scanner.js +309 -0
  9. package/dist/db-scanner.d.ts +7 -0
  10. package/dist/db-scanner.js +185 -0
  11. package/dist/fixers/fix-csrf.d.ts +9 -0
  12. package/dist/fixers/fix-csrf.js +113 -0
  13. package/dist/fixers/fix-gitignore.d.ts +9 -0
  14. package/dist/fixers/fix-gitignore.js +71 -0
  15. package/dist/fixers/fix-headers.d.ts +9 -0
  16. package/dist/fixers/fix-headers.js +118 -0
  17. package/dist/fixers/fix-pii-encrypt.d.ts +9 -0
  18. package/dist/fixers/fix-pii-encrypt.js +298 -0
  19. package/dist/fixers/fix-rate-limit.d.ts +9 -0
  20. package/dist/fixers/fix-rate-limit.js +102 -0
  21. package/dist/fixers/fix-rls.d.ts +9 -0
  22. package/dist/fixers/fix-rls.js +243 -0
  23. package/dist/fixers/fix-routes-auth.d.ts +9 -0
  24. package/dist/fixers/fix-routes-auth.js +82 -0
  25. package/dist/fixers/fix-secrets.d.ts +9 -0
  26. package/dist/fixers/fix-secrets.js +132 -0
  27. package/dist/fixers/index.d.ts +11 -0
  28. package/dist/fixers/index.js +37 -0
  29. package/dist/fixers/registry.d.ts +25 -0
  30. package/dist/fixers/registry.js +249 -0
  31. package/dist/fixers/scheduler.d.ts +9 -0
  32. package/dist/fixers/scheduler.js +254 -0
  33. package/dist/fixers/types.d.ts +160 -0
  34. package/dist/fixers/types.js +11 -0
  35. package/dist/reporter.d.ts +28 -0
  36. package/dist/reporter.js +185 -0
  37. package/dist/types.d.ts +154 -0
  38. package/dist/types.js +5 -0
  39. package/package.json +61 -0
package/dist/cli.js ADDED
@@ -0,0 +1,458 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ // ═══════════════════════════════════════════════════════════
4
+ // @nodatachat/guard — CLI Entry Point
5
+ //
6
+ // Usage:
7
+ // npx nodata-guard --license-key NDC-XXXX
8
+ // npx nodata-guard --license-key NDC-XXXX --db postgres://...
9
+ // npx nodata-guard --license-key NDC-XXXX --ci --fail-on critical
10
+ // npx nodata-guard --license-key $NDC_LICENSE --full --db $DATABASE_URL
11
+ //
12
+ // Outputs:
13
+ // ./nodata-full-report.json — Full report (stays local)
14
+ // ./nodata-metadata-only.json — Metadata sent to NoData
15
+ //
16
+ // The customer can diff the two files to verify no data was sent.
17
+ // ═══════════════════════════════════════════════════════════
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ const path_1 = require("path");
20
+ const fs_1 = require("fs");
21
+ const activation_1 = require("./activation");
22
+ const code_scanner_1 = require("./code-scanner");
23
+ const db_scanner_1 = require("./db-scanner");
24
+ const reporter_1 = require("./reporter");
25
+ const registry_1 = require("./fixers/registry");
26
+ const scheduler_1 = require("./fixers/scheduler");
27
+ const VERSION = "2.0.0";
28
+ async function main() {
29
+ const args = process.argv.slice(2);
30
+ // Parse args
31
+ let licenseKey;
32
+ let dbUrl;
33
+ let projectDir = process.cwd();
34
+ let ciMode = false;
35
+ let failOn = null;
36
+ let outputDir = process.cwd();
37
+ let skipSend = false;
38
+ let fixMode = "none";
39
+ let schedulePreset;
40
+ let ciProvider;
41
+ let onlyFixers;
42
+ for (let i = 0; i < args.length; i++) {
43
+ switch (args[i]) {
44
+ case "--license-key":
45
+ licenseKey = args[++i];
46
+ break;
47
+ case "--db":
48
+ dbUrl = args[++i];
49
+ break;
50
+ case "--dir":
51
+ projectDir = (0, path_1.resolve)(args[++i]);
52
+ break;
53
+ case "--output":
54
+ outputDir = (0, path_1.resolve)(args[++i]);
55
+ break;
56
+ case "--ci":
57
+ ciMode = true;
58
+ break;
59
+ case "--fail-on":
60
+ failOn = args[++i];
61
+ break;
62
+ case "--skip-send":
63
+ skipSend = true;
64
+ break;
65
+ case "--fix-plan":
66
+ fixMode = "plan";
67
+ break;
68
+ case "--fix":
69
+ fixMode = "apply";
70
+ break;
71
+ case "--fix-only":
72
+ onlyFixers = args[++i]?.split(",");
73
+ break;
74
+ case "--schedule":
75
+ schedulePreset = args[++i] || "weekly";
76
+ break;
77
+ case "--ci-provider":
78
+ ciProvider = args[++i];
79
+ break;
80
+ case "--help":
81
+ printHelp();
82
+ process.exit(0);
83
+ case "--version":
84
+ console.log(VERSION);
85
+ process.exit(0);
86
+ }
87
+ }
88
+ // Env fallbacks
89
+ if (!licenseKey)
90
+ licenseKey = process.env.NDC_LICENSE || process.env.NODATA_LICENSE_KEY;
91
+ if (!dbUrl)
92
+ dbUrl = process.env.DATABASE_URL;
93
+ if (!licenseKey) {
94
+ console.error("\n Error: --license-key required (or set NDC_LICENSE env var)\n");
95
+ printHelp();
96
+ process.exit(1);
97
+ }
98
+ // ── Schedule setup (no scan needed) ──
99
+ if (schedulePreset) {
100
+ const config = loadOrCreateConfig(projectDir, { dbUrl, failOn });
101
+ config.schedule = { enabled: true, preset: schedulePreset, cron: undefined, timezone: "UTC" };
102
+ const provider = ciProvider || "auto";
103
+ const result = (0, scheduler_1.installSchedule)(projectDir, config, provider);
104
+ console.log("");
105
+ console.log(" ╔══════════════════════════════════════╗");
106
+ console.log(" ║ NoData Guard — Schedule Installed ║");
107
+ console.log(" ╚══════════════════════════════════════╝");
108
+ console.log("");
109
+ result.messages.forEach(m => console.log(` ${m}`));
110
+ console.log("");
111
+ result.files.forEach(f => console.log(` Created: ${f}`));
112
+ console.log("");
113
+ return;
114
+ }
115
+ const scanType = dbUrl ? "full" : "code";
116
+ const startTime = Date.now();
117
+ // ── Header ──
118
+ if (!ciMode) {
119
+ console.log("");
120
+ console.log(" ╔══════════════════════════════════════╗");
121
+ console.log(" ║ NoData Guard v" + VERSION + " ║");
122
+ console.log(" ║ Security scanner — runs locally ║");
123
+ console.log(" ║ Your data never leaves your machine ║");
124
+ console.log(" ╚══════════════════════════════════════╝");
125
+ console.log("");
126
+ }
127
+ // ── Step 1: Activate ──
128
+ log(ciMode, "Activating license...");
129
+ const activation = await (0, activation_1.activate)({
130
+ licenseKey,
131
+ projectDir,
132
+ scanType,
133
+ });
134
+ if (!activation.success) {
135
+ console.error(` Activation failed: ${activation.error}`);
136
+ process.exit(1);
137
+ }
138
+ log(ciMode, `Activated. Tier: ${activation.tier} | Scan ID: ${activation.scan_id}`);
139
+ // ── Step 2: Code scan ──
140
+ log(ciMode, "Scanning code...");
141
+ const files = (0, code_scanner_1.readProjectFiles)(projectDir, (count) => {
142
+ if (!ciMode)
143
+ process.stdout.write(`\r Reading files... ${count}`);
144
+ });
145
+ if (!ciMode)
146
+ console.log("");
147
+ log(ciMode, `Scanned ${files.length} files`);
148
+ const piiFields = (0, code_scanner_1.scanPIIFields)(files);
149
+ const routes = (0, code_scanner_1.scanRoutes)(files);
150
+ const secrets = (0, code_scanner_1.scanSecrets)(files);
151
+ const stack = (0, code_scanner_1.detectStack)(files);
152
+ const encryptedCode = piiFields.filter(f => f.encrypted).length;
153
+ log(ciMode, `PII fields: ${piiFields.length} found, ${encryptedCode} encrypted`);
154
+ log(ciMode, `Routes: ${routes.length} total, ${routes.filter(r => r.has_auth).length} protected`);
155
+ log(ciMode, `Secrets: ${secrets.filter(s => !s.is_env_interpolated).length} hardcoded`);
156
+ // ── Step 3: DB scan (if connection string provided) ──
157
+ let dbResult = null;
158
+ if (dbUrl) {
159
+ log(ciMode, "Scanning database...");
160
+ try {
161
+ dbResult = await (0, db_scanner_1.scanDatabase)(dbUrl, (msg) => log(ciMode, ` DB: ${msg}`));
162
+ const encryptedDb = dbResult.pii_fields.filter(f => f.encrypted).length;
163
+ log(ciMode, `DB PII: ${dbResult.pii_fields.length} found, ${encryptedDb} encrypted`);
164
+ log(ciMode, `RLS: ${dbResult.rls.filter(r => r.rls_enabled).length}/${dbResult.rls.length} tables`);
165
+ }
166
+ catch (err) {
167
+ log(ciMode, `DB scan failed: ${err instanceof Error ? err.message : err}`);
168
+ }
169
+ }
170
+ // ── Step 4: Generate dual reports ──
171
+ const scanDuration = Date.now() - startTime;
172
+ const projectHash = (0, activation_1.computeProjectHash)(projectDir);
173
+ const { full, metadata } = (0, reporter_1.generateReports)({
174
+ scanId: activation.scan_id,
175
+ projectName: projectDir.split(/[/\\]/).pop() || "unknown",
176
+ projectHash,
177
+ scanType,
178
+ scanDurationMs: scanDuration,
179
+ previousScore: null, // Will be filled by API response
180
+ code: {
181
+ filesScanned: files.length,
182
+ framework: stack.framework,
183
+ database: stack.database,
184
+ piiFields,
185
+ routes,
186
+ secrets,
187
+ },
188
+ db: dbResult ? {
189
+ tables: dbResult.tables,
190
+ piiFields: dbResult.pii_fields,
191
+ rls: dbResult.rls,
192
+ infra: dbResult.infra,
193
+ } : undefined,
194
+ });
195
+ // ── Step 5: Save reports ──
196
+ const fullPath = (0, path_1.resolve)(outputDir, "nodata-full-report.json");
197
+ const metaPath = (0, path_1.resolve)(outputDir, "nodata-metadata-only.json");
198
+ (0, fs_1.writeFileSync)(fullPath, JSON.stringify(full, null, 2), "utf-8");
199
+ (0, fs_1.writeFileSync)(metaPath, JSON.stringify(metadata, null, 2), "utf-8");
200
+ log(ciMode, `Full report: ${fullPath}`);
201
+ log(ciMode, `Metadata only: ${metaPath}`);
202
+ // ── Step 6: Send metadata to NoData ──
203
+ let serverResponse = {};
204
+ if (!skipSend) {
205
+ log(ciMode, "Sending metadata to NoData...");
206
+ try {
207
+ const res = await fetch("https://nodatachat.com/api/guard/report", {
208
+ method: "POST",
209
+ headers: {
210
+ "Content-Type": "application/json",
211
+ "X-Scan-Id": activation.scan_id,
212
+ "X-Activation-Key": activation.activation_key,
213
+ },
214
+ body: JSON.stringify(metadata),
215
+ signal: AbortSignal.timeout(10000),
216
+ });
217
+ if (res.ok) {
218
+ serverResponse = await res.json();
219
+ log(ciMode, `Sent. ${serverResponse.improved ? "Score improved!" : "Score tracked."}`);
220
+ }
221
+ else {
222
+ log(ciMode, `Send failed (${res.status}) — report saved locally`);
223
+ }
224
+ }
225
+ catch {
226
+ log(ciMode, "Could not reach NoData API — report saved locally");
227
+ }
228
+ }
229
+ // ── Step 7: Print summary ──
230
+ if (!ciMode) {
231
+ console.log("");
232
+ console.log(" ══════════════════════════════════════");
233
+ console.log(" GUARD RESULTS");
234
+ console.log(" ══════════════════════════════════════");
235
+ console.log(` Score: ${full.overall_score}%${serverResponse.previous_score != null ? ` (was ${serverResponse.previous_score}%)` : ""}`);
236
+ console.log(` PII fields: ${full.summary.encrypted_fields}/${full.summary.total_pii_fields} encrypted (${full.summary.coverage_percent}%)`);
237
+ if (full.code) {
238
+ console.log(` Routes: ${full.code.routes.filter(r => r.has_auth).length}/${full.code.routes.length} protected`);
239
+ console.log(` Secrets: ${full.summary.critical_issues} critical, ${full.summary.high_issues} high`);
240
+ }
241
+ if (full.db) {
242
+ console.log(` DB encryption: ${full.db.encryption_coverage_percent}%`);
243
+ console.log(` RLS coverage: ${full.db.rls_coverage_percent}%`);
244
+ }
245
+ console.log(" ──────────────────────────────────────");
246
+ console.log(` Full report: ${fullPath}`);
247
+ console.log(` Sent to NoData: ${metaPath}`);
248
+ console.log(` Proof hash: ${full.proof_hash.slice(0, 16)}...`);
249
+ console.log(" ══════════════════════════════════════");
250
+ console.log(" Your data never left your machine.");
251
+ console.log(" Diff the two files to verify.\n");
252
+ }
253
+ // ── Step 8: Capsule — Fix Plan / Apply ──
254
+ if (fixMode !== "none") {
255
+ const config = loadOrCreateConfig(projectDir, { dbUrl, failOn });
256
+ const fixerContext = {
257
+ projectDir,
258
+ dbUrl,
259
+ config,
260
+ scanResults: {
261
+ piiFields: piiFields.map(f => ({
262
+ table: f.table, column: f.column, pii_type: f.pii_type,
263
+ encrypted: f.encrypted, encryption_pattern: f.encryption_pattern,
264
+ })),
265
+ routes: routes.map(r => ({ path: r.path, has_auth: r.has_auth, auth_type: r.auth_type })),
266
+ secrets: secrets.map(s => ({
267
+ file: s.file, line: s.line, type: s.type,
268
+ severity: s.severity, is_env_interpolated: s.is_env_interpolated,
269
+ })),
270
+ rls: dbResult?.rls || [],
271
+ framework: stack.framework,
272
+ database: stack.database,
273
+ },
274
+ stack: {
275
+ framework: stack.framework,
276
+ database: stack.database,
277
+ language: "typescript",
278
+ hosting: "vercel",
279
+ },
280
+ };
281
+ const capsuleResult = await (0, registry_1.runCapsule)(fixerContext, {
282
+ mode: fixMode === "plan" ? "plan" : "apply",
283
+ fixers: onlyFixers,
284
+ dryRun: fixMode === "plan",
285
+ }, (msg) => log(ciMode, msg));
286
+ if (!ciMode) {
287
+ console.log("");
288
+ console.log(" ══════════════════════════════════════");
289
+ console.log(` CAPSULE ${fixMode === "plan" ? "PLAN" : "RESULTS"}`);
290
+ console.log(" ══════════════════════════════════════");
291
+ console.log(` Total actions: ${capsuleResult.totalActions}`);
292
+ if (fixMode === "apply") {
293
+ console.log(` Applied: ${capsuleResult.applied}`);
294
+ console.log(` Failed: ${capsuleResult.failed}`);
295
+ console.log(` Skipped: ${capsuleResult.skipped}`);
296
+ }
297
+ console.log(` Manual pending: ${capsuleResult.manualPending}`);
298
+ console.log(` Est. impact: +${capsuleResult.estimatedScoreImpact}% score`);
299
+ console.log(` Proof hash: ${capsuleResult.proofHash.slice(0, 16)}...`);
300
+ console.log(" ──────────────────────────────────────");
301
+ // Print per-fixer summary
302
+ for (const plan of capsuleResult.plans) {
303
+ const icon = plan.actions.every(a => a.status === "applied") ? "✅"
304
+ : plan.actions.some(a => a.status === "failed") ? "❌" : "📋";
305
+ console.log(` ${icon} ${plan.nameHe}: ${plan.totalActions} actions (${plan.autoFixable} auto, ${plan.manualRequired} manual)`);
306
+ }
307
+ console.log(" ══════════════════════════════════════\n");
308
+ // Write fix plans to output dir
309
+ if (capsuleResult.plans.length > 0) {
310
+ const plansPath = (0, path_1.resolve)(outputDir, "nodata-fix-plan.json");
311
+ (0, fs_1.writeFileSync)(plansPath, JSON.stringify(capsuleResult, null, 2), "utf-8");
312
+ log(ciMode, `Fix plan saved: ${plansPath}`);
313
+ // Write SQL migrations to files
314
+ for (const plan of capsuleResult.plans) {
315
+ for (const action of plan.actions) {
316
+ if (action.type === "file-create" && action.target.includes("migrations/")) {
317
+ const migPath = (0, path_1.resolve)(outputDir, action.target);
318
+ const migDir = (0, path_1.resolve)(outputDir, "migrations");
319
+ if (!(0, fs_1.existsSync)(migDir)) {
320
+ const { mkdirSync } = require("fs");
321
+ mkdirSync(migDir, { recursive: true });
322
+ }
323
+ (0, fs_1.writeFileSync)(migPath, action.content, "utf-8");
324
+ log(ciMode, `Migration: ${migPath}`);
325
+ }
326
+ }
327
+ }
328
+ }
329
+ }
330
+ // Send notifications if configured
331
+ if (config.notify && fixMode === "apply") {
332
+ const notifyPayload = {
333
+ event: capsuleResult.applied > 0 ? "fix-applied" : "scan-complete",
334
+ timestamp: new Date().toISOString(),
335
+ projectName: full.project_name || "unknown",
336
+ projectHash: full.proof_hash?.slice(0, 16) || "",
337
+ score: full.overall_score,
338
+ previousScore: serverResponse.previous_score ?? null,
339
+ delta: serverResponse.previous_score != null ? full.overall_score - serverResponse.previous_score : 0,
340
+ critical: full.summary.critical_issues,
341
+ high: full.summary.high_issues,
342
+ medium: full.summary.medium_issues,
343
+ fixesApplied: capsuleResult.applied,
344
+ fixesFailed: capsuleResult.failed,
345
+ scanId: activation.scan_id,
346
+ proofHash: capsuleResult.proofHash,
347
+ };
348
+ await (0, registry_1.sendNotifications)(config, notifyPayload.event, notifyPayload, (msg) => log(ciMode, msg));
349
+ }
350
+ }
351
+ // ── CI mode: exit code ──
352
+ if (ciMode && failOn) {
353
+ const { critical_issues, high_issues, medium_issues } = full.summary;
354
+ let shouldFail = false;
355
+ if (failOn === "critical" && critical_issues > 0)
356
+ shouldFail = true;
357
+ if (failOn === "high" && (critical_issues + high_issues) > 0)
358
+ shouldFail = true;
359
+ if (failOn === "medium" && (critical_issues + high_issues + medium_issues) > 0)
360
+ shouldFail = true;
361
+ if (shouldFail) {
362
+ console.log(`::error::NoData Guard: ${critical_issues} critical, ${high_issues} high, ${medium_issues} medium issues`);
363
+ process.exit(1);
364
+ }
365
+ else {
366
+ console.log(`NoData Guard: PASS (score ${full.overall_score}%)`);
367
+ }
368
+ }
369
+ }
370
+ function log(ci, msg) {
371
+ if (ci) {
372
+ console.log(`[nodata-guard] ${msg}`);
373
+ }
374
+ else {
375
+ console.log(` ${msg}`);
376
+ }
377
+ }
378
+ function loadOrCreateConfig(projectDir, overrides) {
379
+ const configPath = (0, path_1.resolve)(projectDir, ".nodata-guard.json");
380
+ if ((0, fs_1.existsSync)(configPath)) {
381
+ try {
382
+ return JSON.parse((0, fs_1.readFileSync)(configPath, "utf-8"));
383
+ }
384
+ catch { /* fall through */ }
385
+ }
386
+ return (0, scheduler_1.generateDefaultConfig)({
387
+ scan: {
388
+ dbUrl: overrides.dbUrl,
389
+ failOn: overrides.failOn || "critical",
390
+ },
391
+ });
392
+ }
393
+ function printHelp() {
394
+ console.log(`
395
+ NoData Guard v${VERSION} — Security Scanner + Auto-Remediation Capsule
396
+
397
+ Usage:
398
+ npx nodata-guard --license-key NDC-XXXX # Scan only
399
+ npx nodata-guard --license-key NDC-XXXX --fix-plan # Scan + show fix plan
400
+ npx nodata-guard --license-key NDC-XXXX --fix # Scan + apply fixes
401
+ npx nodata-guard --license-key NDC-XXXX --schedule weekly # Setup CI schedule
402
+ npx nodata-guard --license-key NDC-XXXX --db $DATABASE_URL --fix # Full scan + fix
403
+
404
+ Scan Options:
405
+ --license-key <key> NoData license key (or set NDC_LICENSE env var)
406
+ --db <url> Database connection string for DB probe
407
+ --dir <path> Project directory (default: cwd)
408
+ --output <path> Output directory for reports (default: cwd)
409
+ --ci CI mode — minimal output, exit codes
410
+ --fail-on <level> Exit 1 if issues at: critical | high | medium
411
+ --skip-send Don't send metadata to NoData
412
+
413
+ Capsule Options:
414
+ --fix-plan Generate fix plan (dry-run, no changes)
415
+ --fix Apply all auto-fixable remediation
416
+ --fix-only <fixers> Only run specific fixers (comma-separated)
417
+ Fixers: pii-encrypt, rls, secrets, routes-auth,
418
+ headers, csrf, rate-limit, gitignore
419
+
420
+ Schedule Options:
421
+ --schedule <preset> Install CI workflow: daily | weekly | monthly
422
+ --ci-provider <name> CI provider: github-actions | gitlab-ci | bitbucket | auto
423
+
424
+ Notifications:
425
+ Configure in .nodata-guard.json → notify: { email, webhook, slack, telegram }
426
+
427
+ Output files:
428
+ nodata-full-report.json Full report — STAYS LOCAL
429
+ nodata-metadata-only.json Metadata only — sent to NoData
430
+ nodata-fix-plan.json Fix plan with actions
431
+ migrations/*.sql Generated SQL migrations
432
+
433
+ What we NEVER receive:
434
+ Data values, emails, phones, passwords, source code, connection strings.
435
+
436
+ Examples:
437
+ # Quick scan
438
+ npx nodata-guard --license-key NDC-XXXX
439
+
440
+ # Full scan + DB probe + fix
441
+ npx nodata-guard --license-key NDC-XXXX --db $DATABASE_URL --fix
442
+
443
+ # Only fix security headers and CSRF
444
+ npx nodata-guard --license-key NDC-XXXX --fix --fix-only headers,csrf
445
+
446
+ # Setup weekly CI scan with GitHub Actions
447
+ npx nodata-guard --license-key NDC-XXXX --schedule weekly
448
+
449
+ # CI pipeline
450
+ npx nodata-guard --ci --fail-on critical
451
+
452
+ Documentation: https://nodatachat.com/guard
453
+ `);
454
+ }
455
+ main().catch((err) => {
456
+ console.error("Fatal:", err);
457
+ process.exit(1);
458
+ });
@@ -0,0 +1,14 @@
1
+ import type { PIIFieldResult, RouteResult, SecretResult } from "./types";
2
+ interface FileEntry {
3
+ path: string;
4
+ content: string;
5
+ }
6
+ export declare function readProjectFiles(projectDir: string, onProgress?: (count: number) => void): FileEntry[];
7
+ export declare function scanPIIFields(files: FileEntry[]): PIIFieldResult[];
8
+ export declare function scanRoutes(files: FileEntry[]): RouteResult[];
9
+ export declare function scanSecrets(files: FileEntry[]): SecretResult[];
10
+ export declare function detectStack(files: FileEntry[]): {
11
+ framework: string;
12
+ database: string;
13
+ };
14
+ export {};