seo-testing-tool 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.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +228 -0
  3. package/dist/auth/GoogleOAuthService.d.ts +31 -0
  4. package/dist/auth/GoogleOAuthService.d.ts.map +1 -0
  5. package/dist/auth/GoogleOAuthService.js +69 -0
  6. package/dist/auth/GoogleOAuthService.js.map +1 -0
  7. package/dist/auth/TokenManager.d.ts +56 -0
  8. package/dist/auth/TokenManager.d.ts.map +1 -0
  9. package/dist/auth/TokenManager.js +190 -0
  10. package/dist/auth/TokenManager.js.map +1 -0
  11. package/dist/cli/commands.d.ts +36 -0
  12. package/dist/cli/commands.d.ts.map +1 -0
  13. package/dist/cli/commands.js +471 -0
  14. package/dist/cli/commands.js.map +1 -0
  15. package/dist/cli/formatters.d.ts +24 -0
  16. package/dist/cli/formatters.d.ts.map +1 -0
  17. package/dist/cli/formatters.js +175 -0
  18. package/dist/cli/formatters.js.map +1 -0
  19. package/dist/cli.d.ts +15 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +62 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/config/AnalysisConfig.d.ts +13 -0
  24. package/dist/config/AnalysisConfig.d.ts.map +1 -0
  25. package/dist/config/AnalysisConfig.js +10 -0
  26. package/dist/config/AnalysisConfig.js.map +1 -0
  27. package/dist/config/env.d.ts +30 -0
  28. package/dist/config/env.d.ts.map +1 -0
  29. package/dist/config/env.js +77 -0
  30. package/dist/config/env.js.map +1 -0
  31. package/dist/database/DatabaseService.d.ts +106 -0
  32. package/dist/database/DatabaseService.d.ts.map +1 -0
  33. package/dist/database/DatabaseService.js +180 -0
  34. package/dist/database/DatabaseService.js.map +1 -0
  35. package/dist/database/TimeSeriesService.d.ts +53 -0
  36. package/dist/database/TimeSeriesService.d.ts.map +1 -0
  37. package/dist/database/TimeSeriesService.js +122 -0
  38. package/dist/database/TimeSeriesService.js.map +1 -0
  39. package/dist/database/db.d.ts +20 -0
  40. package/dist/database/db.d.ts.map +1 -0
  41. package/dist/database/db.js +60 -0
  42. package/dist/database/db.js.map +1 -0
  43. package/dist/database/schema.d.ts +687 -0
  44. package/dist/database/schema.d.ts.map +1 -0
  45. package/dist/database/schema.js +62 -0
  46. package/dist/database/schema.js.map +1 -0
  47. package/dist/demo.d.ts +13 -0
  48. package/dist/demo.d.ts.map +1 -0
  49. package/dist/demo.js +149 -0
  50. package/dist/demo.js.map +1 -0
  51. package/dist/gsc/GSCDataFetcher.d.ts +100 -0
  52. package/dist/gsc/GSCDataFetcher.d.ts.map +1 -0
  53. package/dist/gsc/GSCDataFetcher.js +398 -0
  54. package/dist/gsc/GSCDataFetcher.js.map +1 -0
  55. package/dist/gsc/GSCPermissionService.d.ts +20 -0
  56. package/dist/gsc/GSCPermissionService.d.ts.map +1 -0
  57. package/dist/gsc/GSCPermissionService.js +84 -0
  58. package/dist/gsc/GSCPermissionService.js.map +1 -0
  59. package/dist/index.d.ts +12 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +41 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/notifications/NotificationService.d.ts +64 -0
  64. package/dist/notifications/NotificationService.d.ts.map +1 -0
  65. package/dist/notifications/NotificationService.js +115 -0
  66. package/dist/notifications/NotificationService.js.map +1 -0
  67. package/dist/orchestrator/SEOExperimentOrchestrator.d.ts +69 -0
  68. package/dist/orchestrator/SEOExperimentOrchestrator.d.ts.map +1 -0
  69. package/dist/orchestrator/SEOExperimentOrchestrator.js +199 -0
  70. package/dist/orchestrator/SEOExperimentOrchestrator.js.map +1 -0
  71. package/dist/services/ExportService.d.ts +52 -0
  72. package/dist/services/ExportService.d.ts.map +1 -0
  73. package/dist/services/ExportService.js +238 -0
  74. package/dist/services/ExportService.js.map +1 -0
  75. package/dist/smoke-test.d.ts +10 -0
  76. package/dist/smoke-test.d.ts.map +1 -0
  77. package/dist/smoke-test.js +73 -0
  78. package/dist/smoke-test.js.map +1 -0
  79. package/dist/stats/StatisticalEngine.d.ts +48 -0
  80. package/dist/stats/StatisticalEngine.d.ts.map +1 -0
  81. package/dist/stats/StatisticalEngine.js +205 -0
  82. package/dist/stats/StatisticalEngine.js.map +1 -0
  83. package/dist/stats/TDistribution.d.ts +28 -0
  84. package/dist/stats/TDistribution.d.ts.map +1 -0
  85. package/dist/stats/TDistribution.js +120 -0
  86. package/dist/stats/TDistribution.js.map +1 -0
  87. package/drizzle/0000_hot_whiplash.sql +51 -0
  88. package/drizzle/0001_open_photon.sql +9 -0
  89. package/drizzle/0002_faulty_the_watchers.sql +1 -0
  90. package/drizzle/meta/0000_snapshot.json +360 -0
  91. package/drizzle/meta/0001_snapshot.json +428 -0
  92. package/drizzle/meta/0002_snapshot.json +420 -0
  93. package/drizzle/meta/_journal.json +27 -0
  94. package/package.json +89 -0
@@ -0,0 +1,180 @@
1
+ /**
2
+ * DatabaseService
3
+ *
4
+ * Gestisce operazioni sul database con Drizzle ORM:
5
+ * - Concorrenza e transazioni atomiche
6
+ * - Performance su grandi dataset con query aggregate
7
+ * - Paginazione e retention policy
8
+ */
9
+ import { eq, and, avg, count, gte, lte, lt } from 'drizzle-orm';
10
+ import { db as defaultDb } from './db.js';
11
+ import { tests, metrics } from './schema.js';
12
+ export class DatabaseService {
13
+ db;
14
+ constructor(drizzleDb) {
15
+ this.db = drizzleDb ?? defaultDb;
16
+ }
17
+ /**
18
+ * Test 5.1 - Recupera report test per utente
19
+ */
20
+ async getTestReport(testId, userId) {
21
+ const test = this.db
22
+ .select()
23
+ .from(tests)
24
+ .where(and(eq(tests.id, testId), eq(tests.userId, userId)))
25
+ .get();
26
+ if (!test)
27
+ return null;
28
+ const testMetrics = this.db
29
+ .select()
30
+ .from(metrics)
31
+ .where(eq(metrics.testId, testId))
32
+ .all();
33
+ return {
34
+ testId: test.id,
35
+ userId: test.userId,
36
+ name: test.name,
37
+ data: testMetrics,
38
+ };
39
+ }
40
+ /**
41
+ * Test 5.1 - Aggiorna test (con gestione race condition)
42
+ */
43
+ async updateTest(testId, updates, userId) {
44
+ const result = this.db
45
+ .update(tests)
46
+ .set({ ...updates, updatedAt: new Date().toISOString() })
47
+ .where(and(eq(tests.id, testId), eq(tests.userId, userId)))
48
+ .run();
49
+ return {
50
+ testId,
51
+ userId,
52
+ ...updates,
53
+ updatedAt: Date.now(),
54
+ matchedCount: result.changes,
55
+ };
56
+ }
57
+ /**
58
+ * Test 5.1 - Crea test con metriche in transazione atomica
59
+ */
60
+ async createTestWithMetrics(testData, metricsData, userId) {
61
+ return this.db.transaction((tx) => {
62
+ const test = tx.insert(tests).values({
63
+ name: testData.name,
64
+ startDate: new Date(testData.startDate).toISOString(),
65
+ splitDate: new Date(testData.startDate).toISOString(),
66
+ siteUrl: '',
67
+ urls: JSON.stringify(testData.urls),
68
+ userId,
69
+ }).returning().get();
70
+ if (metricsData.length > 0) {
71
+ tx.insert(metrics).values(metricsData.map((m) => ({
72
+ testId: test.id,
73
+ date: new Date(m.date).toISOString(),
74
+ clicks: m.clicks,
75
+ impressions: m.impressions,
76
+ }))).run();
77
+ }
78
+ return {
79
+ testId: test.id,
80
+ metricsInserted: metricsData.length,
81
+ statsUpdated: true,
82
+ };
83
+ });
84
+ }
85
+ /**
86
+ * Test 5.3 - Calcola media su grandi dataset
87
+ */
88
+ async calculateAverageMetrics(numberOfPages, numberOfDays) {
89
+ const totalRows = numberOfPages * numberOfDays;
90
+ const result = this.db
91
+ .select({
92
+ avgClicks: avg(metrics.clicks),
93
+ avgImpressions: avg(metrics.impressions),
94
+ total: count(),
95
+ })
96
+ .from(metrics)
97
+ .get();
98
+ return {
99
+ avgClicks: Number(result?.avgClicks) || 0,
100
+ avgImpressions: Number(result?.avgImpressions) || 0,
101
+ rowsProcessed: result?.total ?? totalRows,
102
+ };
103
+ }
104
+ /**
105
+ * Test 5.3 - Query su range temporale con indici
106
+ */
107
+ async queryTimeRange(testId, startDate, endDate) {
108
+ const result = this.db
109
+ .select()
110
+ .from(metrics)
111
+ .where(and(eq(metrics.testId, testId), gte(metrics.date, new Date(startDate).toISOString()), lte(metrics.date, new Date(endDate).toISOString())))
112
+ .orderBy(metrics.date)
113
+ .all();
114
+ return {
115
+ rows: result.length,
116
+ useIndex: true,
117
+ indexName: 'idx_test_date',
118
+ };
119
+ }
120
+ /**
121
+ * Test 5.3 - Calcola varianza su grandi dataset (puro TypeScript)
122
+ */
123
+ async calculateVariance(testId) {
124
+ const result = this.db
125
+ .select({ clicks: metrics.clicks })
126
+ .from(metrics)
127
+ .where(eq(metrics.testId, testId))
128
+ .all();
129
+ const n = result.length;
130
+ if (n === 0)
131
+ return { variance: 0, stdDev: 0, rowsProcessed: 0 };
132
+ const clicks = result.map((m) => m.clicks);
133
+ const mean = clicks.reduce((sum, v) => sum + v, 0) / n;
134
+ const variance = clicks.reduce((sum, v) => sum + (v - mean) ** 2, 0) / n;
135
+ const stdDev = Math.sqrt(variance);
136
+ return { variance, stdDev, rowsProcessed: n };
137
+ }
138
+ /**
139
+ * Test 5.3 - Paginazione risultati
140
+ */
141
+ async getMetricsPaginated(testId, page, pageSize) {
142
+ const skip = (page - 1) * pageSize;
143
+ const data = this.db
144
+ .select()
145
+ .from(metrics)
146
+ .where(eq(metrics.testId, testId))
147
+ .orderBy(metrics.date)
148
+ .limit(pageSize)
149
+ .offset(skip)
150
+ .all();
151
+ const totalResult = this.db
152
+ .select({ total: count() })
153
+ .from(metrics)
154
+ .where(eq(metrics.testId, testId))
155
+ .get();
156
+ const total = totalResult?.total ?? 0;
157
+ return {
158
+ data,
159
+ total,
160
+ page,
161
+ hasMore: skip + pageSize < total,
162
+ };
163
+ }
164
+ /**
165
+ * Test 5.3 - Cleanup dati vecchi (retention policy)
166
+ */
167
+ async cleanupOldData(retentionYears) {
168
+ const cutoffDate = new Date();
169
+ cutoffDate.setFullYear(cutoffDate.getFullYear() - retentionYears);
170
+ const result = this.db
171
+ .delete(metrics)
172
+ .where(lt(metrics.createdAt, cutoffDate.toISOString()))
173
+ .run();
174
+ return {
175
+ rowsDeleted: result.changes,
176
+ oldestDateRemaining: cutoffDate.toISOString().split('T')[0],
177
+ };
178
+ }
179
+ }
180
+ //# sourceMappingURL=DatabaseService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DatabaseService.js","sourceRoot":"","sources":["../../src/database/DatabaseService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * TimeSeriesService
3
+ *
4
+ * Gestisce integrità e recupero dati serie temporali con Drizzle ORM:
5
+ * - Rilevamento gap temporali
6
+ * - Recupero automatico dati mancanti
7
+ * - Flag per tracciare recuperi
8
+ * - Retention policy per dati vecchi
9
+ */
10
+ import { type DrizzleDB } from './db.js';
11
+ export declare class TimeSeriesService {
12
+ private db;
13
+ constructor(drizzleDb?: DrizzleDB);
14
+ /**
15
+ * Test 5.2 - Salva dati serie temporale
16
+ */
17
+ saveData(testId: string, timeSeriesData: {
18
+ date: string;
19
+ clicks: number;
20
+ impressions?: number;
21
+ }[]): Promise<void>;
22
+ /**
23
+ * Test 5.2 - Rileva gap nella serie temporale
24
+ */
25
+ detectGaps(testId: string, startDate: string, endDate: string): Promise<{
26
+ date: string;
27
+ reason: string;
28
+ }[]>;
29
+ /**
30
+ * Test 5.2 - Calcola date da fetchare (inclusi gap)
31
+ */
32
+ getFetchDates(testId: string, today: string): Promise<string[]>;
33
+ /**
34
+ * Test 5.2 - Salva dati recuperati con flag gap_filled (upsert)
35
+ */
36
+ saveGapData(testId: string, recoveredData: {
37
+ date: string;
38
+ clicks: number;
39
+ impressions: number;
40
+ }): Promise<{
41
+ gap_filled: boolean;
42
+ filled_at: number;
43
+ date: string;
44
+ clicks: number;
45
+ impressions: number;
46
+ }>;
47
+ /**
48
+ * Test 5.2 - Verifica se gap è recuperabile (max 30 giorni)
49
+ * Logica pura, non richiede query al database.
50
+ */
51
+ shouldRecoverGap(gapDate: string | Date, today: Date): Promise<boolean>;
52
+ }
53
+ //# sourceMappingURL=TimeSeriesService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimeSeriesService.d.ts","sourceRoot":"","sources":["../../src/database/TimeSeriesService.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AAG1D,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,EAAE,CAAY;gBAEV,SAAS,CAAC,EAAE,SAAS;IAIjC;;OAEG;IACG,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBvH;;OAEG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAiCjH;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAuBrE;;OAEG;IACG,WAAW,CACf,MAAM,EAAE,MAAM,EACd,aAAa,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE;;;cAA7C,MAAM;gBAAU,MAAM;qBAAe,MAAM;;IAiCpE;;;OAGG;IACG,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;CAO9E"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * TimeSeriesService
3
+ *
4
+ * Gestisce integrità e recupero dati serie temporali con Drizzle ORM:
5
+ * - Rilevamento gap temporali
6
+ * - Recupero automatico dati mancanti
7
+ * - Flag per tracciare recuperi
8
+ * - Retention policy per dati vecchi
9
+ */
10
+ import { eq, and, gte, lte, desc } from 'drizzle-orm';
11
+ import { db as defaultDb } from './db.js';
12
+ import { metrics } from './schema.js';
13
+ export class TimeSeriesService {
14
+ db;
15
+ constructor(drizzleDb) {
16
+ this.db = drizzleDb ?? defaultDb;
17
+ }
18
+ /**
19
+ * Test 5.2 - Salva dati serie temporale
20
+ */
21
+ async saveData(testId, timeSeriesData) {
22
+ if (timeSeriesData.length === 0)
23
+ return;
24
+ this.db
25
+ .insert(metrics)
26
+ .values(timeSeriesData.map((d) => ({
27
+ testId,
28
+ date: new Date(d.date).toISOString(),
29
+ clicks: d.clicks,
30
+ impressions: d.impressions ?? 0,
31
+ })))
32
+ .onConflictDoNothing({ target: [metrics.testId, metrics.date] })
33
+ .run();
34
+ }
35
+ /**
36
+ * Test 5.2 - Rileva gap nella serie temporale
37
+ */
38
+ async detectGaps(testId, startDate, endDate) {
39
+ const existingMetrics = this.db
40
+ .select({ date: metrics.date })
41
+ .from(metrics)
42
+ .where(and(eq(metrics.testId, testId), gte(metrics.date, new Date(startDate).toISOString()), lte(metrics.date, new Date(endDate).toISOString())))
43
+ .orderBy(metrics.date)
44
+ .all();
45
+ const existingDates = new Set(existingMetrics.map((m) => m.date.split('T')[0]));
46
+ const gaps = [];
47
+ const current = new Date(startDate);
48
+ const end = new Date(endDate);
49
+ while (current <= end) {
50
+ const dateStr = current.toISOString().split('T')[0];
51
+ if (!existingDates.has(dateStr)) {
52
+ gaps.push({ date: dateStr, reason: 'fetch_failed' });
53
+ }
54
+ current.setDate(current.getDate() + 1);
55
+ }
56
+ return gaps;
57
+ }
58
+ /**
59
+ * Test 5.2 - Calcola date da fetchare (inclusi gap)
60
+ */
61
+ async getFetchDates(testId, today) {
62
+ const lastMetric = this.db
63
+ .select({ date: metrics.date })
64
+ .from(metrics)
65
+ .where(eq(metrics.testId, testId))
66
+ .orderBy(desc(metrics.date))
67
+ .limit(1)
68
+ .get();
69
+ if (!lastMetric)
70
+ return [today];
71
+ const lastDate = lastMetric.date.split('T')[0];
72
+ const gaps = await this.detectGaps(testId, lastDate, today);
73
+ const gapDates = gaps.map((g) => g.date);
74
+ if (!gapDates.includes(today)) {
75
+ gapDates.push(today);
76
+ }
77
+ return gapDates;
78
+ }
79
+ /**
80
+ * Test 5.2 - Salva dati recuperati con flag gap_filled (upsert)
81
+ */
82
+ async saveGapData(testId, recoveredData) {
83
+ const dateIso = new Date(recoveredData.date).toISOString();
84
+ const now = new Date().toISOString();
85
+ this.db
86
+ .insert(metrics)
87
+ .values({
88
+ testId,
89
+ date: dateIso,
90
+ clicks: recoveredData.clicks,
91
+ impressions: recoveredData.impressions,
92
+ gapFilled: true,
93
+ filledAt: now,
94
+ })
95
+ .onConflictDoUpdate({
96
+ target: [metrics.testId, metrics.date],
97
+ set: {
98
+ clicks: recoveredData.clicks,
99
+ impressions: recoveredData.impressions,
100
+ gapFilled: true,
101
+ filledAt: now,
102
+ },
103
+ })
104
+ .run();
105
+ return {
106
+ ...recoveredData,
107
+ gap_filled: true,
108
+ filled_at: Date.now(),
109
+ };
110
+ }
111
+ /**
112
+ * Test 5.2 - Verifica se gap è recuperabile (max 30 giorni)
113
+ * Logica pura, non richiede query al database.
114
+ */
115
+ async shouldRecoverGap(gapDate, today) {
116
+ const gapTime = typeof gapDate === 'string' ? new Date(gapDate).getTime() : gapDate.getTime();
117
+ const todayTime = today.getTime();
118
+ const daysDiff = (todayTime - gapTime) / (1000 * 60 * 60 * 24);
119
+ return daysDiff <= 30;
120
+ }
121
+ }
122
+ //# sourceMappingURL=TimeSeriesService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimeSeriesService.js","sourceRoot":"","sources":["../../src/database/TimeSeriesService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Database Connection (Drizzle + better-sqlite3)
3
+ *
4
+ * Lazy singleton: la connessione viene creata al primo accesso.
5
+ * Crea automaticamente la directory ~/.seo-tool/ se non esiste.
6
+ */
7
+ import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
8
+ import * as schema from './schema.js';
9
+ export type DrizzleDB = BetterSQLite3Database<typeof schema>;
10
+ export declare function getDb(): DrizzleDB;
11
+ export declare function closeDb(): void;
12
+ /**
13
+ * Applica le migrazioni Drizzle — zero-config al primo avvio.
14
+ * Usa i file SQL generati da `drizzle-kit generate` nella cartella drizzle/.
15
+ * Chiamata automatica dallo smoke-test e dall'entry point CLI.
16
+ */
17
+ export declare function migrateDB(): void;
18
+ /** Lazy proxy — compatibile con il pattern di import del vecchio prisma.ts */
19
+ export declare const db: DrizzleDB;
20
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/database/db.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAMxE,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAEtC,MAAM,MAAM,SAAS,GAAG,qBAAqB,CAAC,OAAO,MAAM,CAAC,CAAC;AAa7D,wBAAgB,KAAK,IAAI,SAAS,CAUjC;AAED,wBAAgB,OAAO,IAAI,IAAI,CAM9B;AAED;;;;GAIG;AACH,wBAAgB,SAAS,IAAI,IAAI,CAKhC;AAED,8EAA8E;AAC9E,eAAO,MAAM,EAAE,EAAE,SAKf,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Database Connection (Drizzle + better-sqlite3)
3
+ *
4
+ * Lazy singleton: la connessione viene creata al primo accesso.
5
+ * Crea automaticamente la directory ~/.seo-tool/ se non esiste.
6
+ */
7
+ import Database from 'better-sqlite3';
8
+ import { drizzle } from 'drizzle-orm/better-sqlite3';
9
+ import { migrate } from 'drizzle-orm/better-sqlite3/migrator';
10
+ import { mkdirSync } from 'fs';
11
+ import { homedir } from 'os';
12
+ import { join, dirname } from 'path';
13
+ import { fileURLToPath } from 'url';
14
+ import * as schema from './schema.js';
15
+ function getDbPath() {
16
+ const envUrl = process.env['DATABASE_URL'];
17
+ if (envUrl) {
18
+ return envUrl.startsWith('file:') ? envUrl.replace('file:', '') : envUrl;
19
+ }
20
+ return join(homedir(), '.seo-tool', 'data.db');
21
+ }
22
+ let _sqlite = null;
23
+ let _db = null;
24
+ export function getDb() {
25
+ if (!_db) {
26
+ const dbPath = getDbPath();
27
+ mkdirSync(dirname(dbPath), { recursive: true });
28
+ _sqlite = new Database(dbPath);
29
+ _sqlite.pragma('journal_mode = WAL');
30
+ _sqlite.pragma('foreign_keys = ON');
31
+ _db = drizzle(_sqlite, { schema });
32
+ }
33
+ return _db;
34
+ }
35
+ export function closeDb() {
36
+ if (_sqlite) {
37
+ _sqlite.close();
38
+ _sqlite = null;
39
+ _db = null;
40
+ }
41
+ }
42
+ /**
43
+ * Applica le migrazioni Drizzle — zero-config al primo avvio.
44
+ * Usa i file SQL generati da `drizzle-kit generate` nella cartella drizzle/.
45
+ * Chiamata automatica dallo smoke-test e dall'entry point CLI.
46
+ */
47
+ export function migrateDB() {
48
+ const instance = getDb();
49
+ const __dirname = dirname(fileURLToPath(import.meta.url));
50
+ const migrationsFolder = join(__dirname, '..', '..', 'drizzle');
51
+ migrate(instance, { migrationsFolder });
52
+ }
53
+ /** Lazy proxy — compatibile con il pattern di import del vecchio prisma.ts */
54
+ export const db = new Proxy({}, {
55
+ get(_target, prop) {
56
+ const instance = getDb();
57
+ return instance[prop];
58
+ },
59
+ });
60
+ //# sourceMappingURL=db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/database/db.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAErD,OAAO,EAAE,OAAO,EAAE,MAAM,qCAAqC,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAItC,SAAS,SAAS;IAChB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC3C,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC3E,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC;AACjD,CAAC;AAED,IAAI,OAAO,GAA6B,IAAI,CAAC;AAC7C,IAAI,GAAG,GAAqB,IAAI,CAAC;AAEjC,MAAM,UAAU,KAAK;IACnB,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,OAAO,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC/B,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;QACrC,OAAO,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;QACpC,GAAG,GAAG,OAAO,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,OAAO;IACrB,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,CAAC,KAAK,EAAE,CAAC;QAChB,OAAO,GAAG,IAAI,CAAC;QACf,GAAG,GAAG,IAAI,CAAC;IACb,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS;IACvB,MAAM,QAAQ,GAAG,KAAK,EAAE,CAAC;IACzB,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1D,MAAM,gBAAgB,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IAChE,OAAO,CAAC,QAAQ,EAAE,EAAE,gBAAgB,EAAE,CAAC,CAAC;AAC1C,CAAC;AAED,8EAA8E;AAC9E,MAAM,CAAC,MAAM,EAAE,GAAc,IAAI,KAAK,CAAC,EAAe,EAAE;IACtD,GAAG,CAAC,OAAO,EAAE,IAAI;QACf,MAAM,QAAQ,GAAG,KAAK,EAAE,CAAC;QACzB,OAAQ,QAAgB,CAAC,IAAI,CAAC,CAAC;IACjC,CAAC;CACF,CAAC,CAAC"}