@hasna/microservices 0.0.2 → 0.0.4

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 (89) hide show
  1. package/bin/index.js +70 -0
  2. package/bin/mcp.js +71 -1
  3. package/dist/index.js +70 -0
  4. package/microservices/microservice-ads/package.json +27 -0
  5. package/microservices/microservice-ads/src/cli/index.ts +407 -0
  6. package/microservices/microservice-ads/src/db/campaigns.ts +493 -0
  7. package/microservices/microservice-ads/src/db/database.ts +93 -0
  8. package/microservices/microservice-ads/src/db/migrations.ts +60 -0
  9. package/microservices/microservice-ads/src/index.ts +39 -0
  10. package/microservices/microservice-ads/src/mcp/index.ts +320 -0
  11. package/microservices/microservice-contracts/package.json +27 -0
  12. package/microservices/microservice-contracts/src/cli/index.ts +383 -0
  13. package/microservices/microservice-contracts/src/db/contracts.ts +496 -0
  14. package/microservices/microservice-contracts/src/db/database.ts +93 -0
  15. package/microservices/microservice-contracts/src/db/migrations.ts +58 -0
  16. package/microservices/microservice-contracts/src/index.ts +43 -0
  17. package/microservices/microservice-contracts/src/mcp/index.ts +308 -0
  18. package/microservices/microservice-domains/package.json +27 -0
  19. package/microservices/microservice-domains/src/cli/index.ts +438 -0
  20. package/microservices/microservice-domains/src/db/database.ts +93 -0
  21. package/microservices/microservice-domains/src/db/domains.ts +551 -0
  22. package/microservices/microservice-domains/src/db/migrations.ts +60 -0
  23. package/microservices/microservice-domains/src/index.ts +44 -0
  24. package/microservices/microservice-domains/src/mcp/index.ts +368 -0
  25. package/microservices/microservice-hiring/package.json +27 -0
  26. package/microservices/microservice-hiring/src/cli/index.ts +431 -0
  27. package/microservices/microservice-hiring/src/db/database.ts +93 -0
  28. package/microservices/microservice-hiring/src/db/hiring.ts +582 -0
  29. package/microservices/microservice-hiring/src/db/migrations.ts +68 -0
  30. package/microservices/microservice-hiring/src/index.ts +51 -0
  31. package/microservices/microservice-hiring/src/mcp/index.ts +464 -0
  32. package/microservices/microservice-payments/package.json +27 -0
  33. package/microservices/microservice-payments/src/cli/index.ts +357 -0
  34. package/microservices/microservice-payments/src/db/database.ts +93 -0
  35. package/microservices/microservice-payments/src/db/migrations.ts +63 -0
  36. package/microservices/microservice-payments/src/db/payments.ts +652 -0
  37. package/microservices/microservice-payments/src/index.ts +51 -0
  38. package/microservices/microservice-payments/src/mcp/index.ts +460 -0
  39. package/microservices/microservice-payroll/package.json +27 -0
  40. package/microservices/microservice-payroll/src/cli/index.ts +374 -0
  41. package/microservices/microservice-payroll/src/db/database.ts +93 -0
  42. package/microservices/microservice-payroll/src/db/migrations.ts +69 -0
  43. package/microservices/microservice-payroll/src/db/payroll.ts +741 -0
  44. package/microservices/microservice-payroll/src/index.ts +48 -0
  45. package/microservices/microservice-payroll/src/mcp/index.ts +420 -0
  46. package/microservices/microservice-shipping/package.json +27 -0
  47. package/microservices/microservice-shipping/src/cli/index.ts +398 -0
  48. package/microservices/microservice-shipping/src/db/database.ts +93 -0
  49. package/microservices/microservice-shipping/src/db/migrations.ts +61 -0
  50. package/microservices/microservice-shipping/src/db/shipping.ts +643 -0
  51. package/microservices/microservice-shipping/src/index.ts +53 -0
  52. package/microservices/microservice-shipping/src/mcp/index.ts +385 -0
  53. package/microservices/microservice-social/package.json +27 -0
  54. package/microservices/microservice-social/src/cli/index.ts +447 -0
  55. package/microservices/microservice-social/src/db/database.ts +93 -0
  56. package/microservices/microservice-social/src/db/migrations.ts +55 -0
  57. package/microservices/microservice-social/src/db/social.ts +672 -0
  58. package/microservices/microservice-social/src/index.ts +46 -0
  59. package/microservices/microservice-social/src/mcp/index.ts +435 -0
  60. package/microservices/microservice-subscriptions/package.json +27 -0
  61. package/microservices/microservice-subscriptions/src/cli/index.ts +400 -0
  62. package/microservices/microservice-subscriptions/src/db/database.ts +93 -0
  63. package/microservices/microservice-subscriptions/src/db/migrations.ts +57 -0
  64. package/microservices/microservice-subscriptions/src/db/subscriptions.ts +692 -0
  65. package/microservices/microservice-subscriptions/src/index.ts +41 -0
  66. package/microservices/microservice-subscriptions/src/mcp/index.ts +365 -0
  67. package/microservices/microservice-transcriber/package.json +28 -0
  68. package/microservices/microservice-transcriber/src/cli/index.ts +1347 -0
  69. package/microservices/microservice-transcriber/src/db/annotations.ts +37 -0
  70. package/microservices/microservice-transcriber/src/db/database.ts +82 -0
  71. package/microservices/microservice-transcriber/src/db/migrations.ts +72 -0
  72. package/microservices/microservice-transcriber/src/db/transcripts.ts +395 -0
  73. package/microservices/microservice-transcriber/src/index.ts +43 -0
  74. package/microservices/microservice-transcriber/src/lib/config.ts +77 -0
  75. package/microservices/microservice-transcriber/src/lib/diff.ts +91 -0
  76. package/microservices/microservice-transcriber/src/lib/downloader.ts +570 -0
  77. package/microservices/microservice-transcriber/src/lib/feeds.ts +62 -0
  78. package/microservices/microservice-transcriber/src/lib/live.ts +94 -0
  79. package/microservices/microservice-transcriber/src/lib/notion.ts +129 -0
  80. package/microservices/microservice-transcriber/src/lib/providers.ts +713 -0
  81. package/microservices/microservice-transcriber/src/lib/summarizer.ts +147 -0
  82. package/microservices/microservice-transcriber/src/lib/translator.ts +75 -0
  83. package/microservices/microservice-transcriber/src/lib/webhook.ts +37 -0
  84. package/microservices/microservice-transcriber/src/mcp/index.ts +1070 -0
  85. package/microservices/microservice-transcriber/src/server/index.ts +199 -0
  86. package/package.json +1 -1
  87. package/microservices/microservice-invoices/dashboard/dist/assets/index-Bngq7FNM.css +0 -1
  88. package/microservices/microservice-invoices/dashboard/dist/assets/index-aHW4ARZR.js +0 -124
  89. package/microservices/microservice-invoices/dashboard/dist/index.html +0 -13
@@ -0,0 +1,438 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from "commander";
4
+ import {
5
+ createDomain,
6
+ getDomain,
7
+ listDomains,
8
+ updateDomain,
9
+ deleteDomain,
10
+ searchDomains,
11
+ listExpiring,
12
+ listSslExpiring,
13
+ getDomainStats,
14
+ getByRegistrar,
15
+ createDnsRecord,
16
+ listDnsRecords,
17
+ updateDnsRecord,
18
+ deleteDnsRecord,
19
+ createAlert,
20
+ listAlerts,
21
+ deleteAlert,
22
+ } from "../db/domains.js";
23
+
24
+ const program = new Command();
25
+
26
+ program
27
+ .name("microservice-domains")
28
+ .description("Domain portfolio and DNS management microservice")
29
+ .version("0.0.1");
30
+
31
+ // --- Domains ---
32
+
33
+ program
34
+ .command("add")
35
+ .description("Add a new domain")
36
+ .requiredOption("--name <name>", "Domain name (e.g. example.com)")
37
+ .option("--registrar <registrar>", "Domain registrar")
38
+ .option("--status <status>", "Status (active/expired/transferring/redemption)", "active")
39
+ .option("--registered-at <date>", "Registration date (ISO)")
40
+ .option("--expires-at <date>", "Expiration date (ISO)")
41
+ .option("--no-auto-renew", "Disable auto-renew")
42
+ .option("--nameservers <ns>", "Comma-separated nameservers")
43
+ .option("--ssl-expires-at <date>", "SSL expiration date (ISO)")
44
+ .option("--ssl-issuer <issuer>", "SSL certificate issuer")
45
+ .option("--notes <notes>", "Notes")
46
+ .option("--json", "Output as JSON", false)
47
+ .action((opts) => {
48
+ const domain = createDomain({
49
+ name: opts.name,
50
+ registrar: opts.registrar,
51
+ status: opts.status,
52
+ registered_at: opts.registeredAt,
53
+ expires_at: opts.expiresAt,
54
+ auto_renew: opts.autoRenew,
55
+ nameservers: opts.nameservers
56
+ ? opts.nameservers.split(",").map((s: string) => s.trim())
57
+ : undefined,
58
+ ssl_expires_at: opts.sslExpiresAt,
59
+ ssl_issuer: opts.sslIssuer,
60
+ notes: opts.notes,
61
+ });
62
+
63
+ if (opts.json) {
64
+ console.log(JSON.stringify(domain, null, 2));
65
+ } else {
66
+ console.log(`Created domain: ${domain.name} (${domain.id})`);
67
+ }
68
+ });
69
+
70
+ program
71
+ .command("get")
72
+ .description("Get a domain by ID")
73
+ .argument("<id>", "Domain ID")
74
+ .option("--json", "Output as JSON", false)
75
+ .action((id, opts) => {
76
+ const domain = getDomain(id);
77
+ if (!domain) {
78
+ console.error(`Domain '${id}' not found.`);
79
+ process.exit(1);
80
+ }
81
+
82
+ if (opts.json) {
83
+ console.log(JSON.stringify(domain, null, 2));
84
+ } else {
85
+ console.log(`${domain.name} [${domain.status}]`);
86
+ if (domain.registrar) console.log(` Registrar: ${domain.registrar}`);
87
+ if (domain.expires_at) console.log(` Expires: ${domain.expires_at}`);
88
+ if (domain.ssl_expires_at) console.log(` SSL Expires: ${domain.ssl_expires_at}`);
89
+ if (domain.ssl_issuer) console.log(` SSL Issuer: ${domain.ssl_issuer}`);
90
+ console.log(` Auto-renew: ${domain.auto_renew ? "yes" : "no"}`);
91
+ if (domain.nameservers.length) console.log(` Nameservers: ${domain.nameservers.join(", ")}`);
92
+ if (domain.notes) console.log(` Notes: ${domain.notes}`);
93
+ }
94
+ });
95
+
96
+ program
97
+ .command("list")
98
+ .description("List domains")
99
+ .option("--search <query>", "Search by name, registrar, or notes")
100
+ .option("--status <status>", "Filter by status")
101
+ .option("--registrar <registrar>", "Filter by registrar")
102
+ .option("--limit <n>", "Limit results")
103
+ .option("--json", "Output as JSON", false)
104
+ .action((opts) => {
105
+ const domains = listDomains({
106
+ search: opts.search,
107
+ status: opts.status,
108
+ registrar: opts.registrar,
109
+ limit: opts.limit ? parseInt(opts.limit) : undefined,
110
+ });
111
+
112
+ if (opts.json) {
113
+ console.log(JSON.stringify(domains, null, 2));
114
+ } else {
115
+ if (domains.length === 0) {
116
+ console.log("No domains found.");
117
+ return;
118
+ }
119
+ for (const d of domains) {
120
+ const expires = d.expires_at ? ` (expires ${d.expires_at})` : "";
121
+ console.log(` ${d.name} [${d.status}]${expires}`);
122
+ }
123
+ console.log(`\n${domains.length} domain(s)`);
124
+ }
125
+ });
126
+
127
+ program
128
+ .command("update")
129
+ .description("Update a domain")
130
+ .argument("<id>", "Domain ID")
131
+ .option("--name <name>", "Domain name")
132
+ .option("--registrar <registrar>", "Registrar")
133
+ .option("--status <status>", "Status")
134
+ .option("--registered-at <date>", "Registration date")
135
+ .option("--expires-at <date>", "Expiration date")
136
+ .option("--auto-renew <bool>", "Auto-renew (true/false)")
137
+ .option("--nameservers <ns>", "Comma-separated nameservers")
138
+ .option("--ssl-expires-at <date>", "SSL expiration date")
139
+ .option("--ssl-issuer <issuer>", "SSL issuer")
140
+ .option("--notes <notes>", "Notes")
141
+ .option("--json", "Output as JSON", false)
142
+ .action((id, opts) => {
143
+ const input: Record<string, unknown> = {};
144
+ if (opts.name !== undefined) input.name = opts.name;
145
+ if (opts.registrar !== undefined) input.registrar = opts.registrar;
146
+ if (opts.status !== undefined) input.status = opts.status;
147
+ if (opts.registeredAt !== undefined) input.registered_at = opts.registeredAt;
148
+ if (opts.expiresAt !== undefined) input.expires_at = opts.expiresAt;
149
+ if (opts.autoRenew !== undefined) input.auto_renew = opts.autoRenew === "true";
150
+ if (opts.nameservers !== undefined)
151
+ input.nameservers = opts.nameservers.split(",").map((s: string) => s.trim());
152
+ if (opts.sslExpiresAt !== undefined) input.ssl_expires_at = opts.sslExpiresAt;
153
+ if (opts.sslIssuer !== undefined) input.ssl_issuer = opts.sslIssuer;
154
+ if (opts.notes !== undefined) input.notes = opts.notes;
155
+
156
+ const domain = updateDomain(id, input);
157
+ if (!domain) {
158
+ console.error(`Domain '${id}' not found.`);
159
+ process.exit(1);
160
+ }
161
+
162
+ if (opts.json) {
163
+ console.log(JSON.stringify(domain, null, 2));
164
+ } else {
165
+ console.log(`Updated: ${domain.name}`);
166
+ }
167
+ });
168
+
169
+ program
170
+ .command("delete")
171
+ .description("Delete a domain")
172
+ .argument("<id>", "Domain ID")
173
+ .action((id) => {
174
+ const deleted = deleteDomain(id);
175
+ if (deleted) {
176
+ console.log(`Deleted domain ${id}`);
177
+ } else {
178
+ console.error(`Domain '${id}' not found.`);
179
+ process.exit(1);
180
+ }
181
+ });
182
+
183
+ program
184
+ .command("search")
185
+ .description("Search domains")
186
+ .argument("<query>", "Search term")
187
+ .option("--json", "Output as JSON", false)
188
+ .action((query, opts) => {
189
+ const results = searchDomains(query);
190
+
191
+ if (opts.json) {
192
+ console.log(JSON.stringify(results, null, 2));
193
+ } else {
194
+ if (results.length === 0) {
195
+ console.log(`No domains matching "${query}".`);
196
+ return;
197
+ }
198
+ for (const d of results) {
199
+ console.log(` ${d.name} [${d.status}]`);
200
+ }
201
+ }
202
+ });
203
+
204
+ program
205
+ .command("expiring")
206
+ .description("List domains expiring within N days")
207
+ .option("--days <n>", "Number of days ahead", "30")
208
+ .option("--json", "Output as JSON", false)
209
+ .action((opts) => {
210
+ const days = parseInt(opts.days);
211
+ const domains = listExpiring(days);
212
+
213
+ if (opts.json) {
214
+ console.log(JSON.stringify(domains, null, 2));
215
+ } else {
216
+ if (domains.length === 0) {
217
+ console.log(`No domains expiring within ${days} days.`);
218
+ return;
219
+ }
220
+ console.log(`Domains expiring within ${days} days:`);
221
+ for (const d of domains) {
222
+ console.log(` ${d.name} — expires ${d.expires_at}`);
223
+ }
224
+ }
225
+ });
226
+
227
+ program
228
+ .command("ssl")
229
+ .description("List domains with SSL expiring within N days")
230
+ .option("--days <n>", "Number of days ahead", "30")
231
+ .option("--json", "Output as JSON", false)
232
+ .action((opts) => {
233
+ const days = parseInt(opts.days);
234
+ const domains = listSslExpiring(days);
235
+
236
+ if (opts.json) {
237
+ console.log(JSON.stringify(domains, null, 2));
238
+ } else {
239
+ if (domains.length === 0) {
240
+ console.log(`No SSL certificates expiring within ${days} days.`);
241
+ return;
242
+ }
243
+ console.log(`SSL certificates expiring within ${days} days:`);
244
+ for (const d of domains) {
245
+ console.log(` ${d.name} — SSL expires ${d.ssl_expires_at} (${d.ssl_issuer || "unknown issuer"})`);
246
+ }
247
+ }
248
+ });
249
+
250
+ program
251
+ .command("stats")
252
+ .description("Show domain portfolio statistics")
253
+ .option("--json", "Output as JSON", false)
254
+ .action((opts) => {
255
+ const stats = getDomainStats();
256
+
257
+ if (opts.json) {
258
+ console.log(JSON.stringify(stats, null, 2));
259
+ } else {
260
+ console.log("Domain Portfolio Stats:");
261
+ console.log(` Total: ${stats.total}`);
262
+ console.log(` Active: ${stats.active}`);
263
+ console.log(` Expired: ${stats.expired}`);
264
+ console.log(` Transferring: ${stats.transferring}`);
265
+ console.log(` Redemption: ${stats.redemption}`);
266
+ console.log(` Auto-renew enabled: ${stats.auto_renew_enabled}`);
267
+ console.log(` Expiring (30 days): ${stats.expiring_30_days}`);
268
+ console.log(` SSL expiring (30 days): ${stats.ssl_expiring_30_days}`);
269
+ }
270
+ });
271
+
272
+ // --- DNS Records ---
273
+
274
+ const dnsCmd = program
275
+ .command("dns")
276
+ .description("DNS record management");
277
+
278
+ dnsCmd
279
+ .command("list")
280
+ .description("List DNS records for a domain")
281
+ .argument("<domain-id>", "Domain ID")
282
+ .option("--type <type>", "Filter by record type (A/AAAA/CNAME/MX/TXT/NS/SRV)")
283
+ .option("--json", "Output as JSON", false)
284
+ .action((domainId, opts) => {
285
+ const records = listDnsRecords(domainId, opts.type);
286
+
287
+ if (opts.json) {
288
+ console.log(JSON.stringify(records, null, 2));
289
+ } else {
290
+ if (records.length === 0) {
291
+ console.log("No DNS records found.");
292
+ return;
293
+ }
294
+ for (const r of records) {
295
+ const priority = r.priority !== null ? ` (priority: ${r.priority})` : "";
296
+ console.log(` ${r.type}\t${r.name}\t${r.value}\tTTL:${r.ttl}${priority}`);
297
+ }
298
+ }
299
+ });
300
+
301
+ dnsCmd
302
+ .command("add")
303
+ .description("Add a DNS record")
304
+ .requiredOption("--domain <id>", "Domain ID")
305
+ .requiredOption("--type <type>", "Record type (A/AAAA/CNAME/MX/TXT/NS/SRV)")
306
+ .requiredOption("--name <name>", "Record name")
307
+ .requiredOption("--value <value>", "Record value")
308
+ .option("--ttl <ttl>", "TTL in seconds", "3600")
309
+ .option("--priority <n>", "Priority (for MX/SRV)")
310
+ .option("--json", "Output as JSON", false)
311
+ .action((opts) => {
312
+ const record = createDnsRecord({
313
+ domain_id: opts.domain,
314
+ type: opts.type,
315
+ name: opts.name,
316
+ value: opts.value,
317
+ ttl: parseInt(opts.ttl),
318
+ priority: opts.priority ? parseInt(opts.priority) : undefined,
319
+ });
320
+
321
+ if (opts.json) {
322
+ console.log(JSON.stringify(record, null, 2));
323
+ } else {
324
+ console.log(`Created DNS record: ${record.type} ${record.name} -> ${record.value} (${record.id})`);
325
+ }
326
+ });
327
+
328
+ dnsCmd
329
+ .command("update")
330
+ .description("Update a DNS record")
331
+ .argument("<id>", "Record ID")
332
+ .option("--type <type>", "Record type")
333
+ .option("--name <name>", "Record name")
334
+ .option("--value <value>", "Record value")
335
+ .option("--ttl <ttl>", "TTL in seconds")
336
+ .option("--priority <n>", "Priority")
337
+ .option("--json", "Output as JSON", false)
338
+ .action((id, opts) => {
339
+ const input: Record<string, unknown> = {};
340
+ if (opts.type !== undefined) input.type = opts.type;
341
+ if (opts.name !== undefined) input.name = opts.name;
342
+ if (opts.value !== undefined) input.value = opts.value;
343
+ if (opts.ttl !== undefined) input.ttl = parseInt(opts.ttl);
344
+ if (opts.priority !== undefined) input.priority = parseInt(opts.priority);
345
+
346
+ const record = updateDnsRecord(id, input);
347
+ if (!record) {
348
+ console.error(`DNS record '${id}' not found.`);
349
+ process.exit(1);
350
+ }
351
+
352
+ if (opts.json) {
353
+ console.log(JSON.stringify(record, null, 2));
354
+ } else {
355
+ console.log(`Updated DNS record: ${record.type} ${record.name} -> ${record.value}`);
356
+ }
357
+ });
358
+
359
+ dnsCmd
360
+ .command("remove")
361
+ .description("Remove a DNS record")
362
+ .argument("<id>", "Record ID")
363
+ .action((id) => {
364
+ const deleted = deleteDnsRecord(id);
365
+ if (deleted) {
366
+ console.log(`Deleted DNS record ${id}`);
367
+ } else {
368
+ console.error(`DNS record '${id}' not found.`);
369
+ process.exit(1);
370
+ }
371
+ });
372
+
373
+ // --- Alerts ---
374
+
375
+ const alertCmd = program
376
+ .command("alert")
377
+ .description("Alert management");
378
+
379
+ alertCmd
380
+ .command("set")
381
+ .description("Set an alert for a domain")
382
+ .requiredOption("--domain <id>", "Domain ID")
383
+ .requiredOption("--type <type>", "Alert type (expiry/ssl_expiry/dns_change)")
384
+ .option("--days-before <n>", "Trigger N days before")
385
+ .option("--json", "Output as JSON", false)
386
+ .action((opts) => {
387
+ const alert = createAlert({
388
+ domain_id: opts.domain,
389
+ type: opts.type,
390
+ trigger_days_before: opts.daysBefore ? parseInt(opts.daysBefore) : undefined,
391
+ });
392
+
393
+ if (opts.json) {
394
+ console.log(JSON.stringify(alert, null, 2));
395
+ } else {
396
+ const daysBefore = alert.trigger_days_before ? ` (${alert.trigger_days_before} days before)` : "";
397
+ console.log(`Created alert: ${alert.type}${daysBefore} for domain ${alert.domain_id} (${alert.id})`);
398
+ }
399
+ });
400
+
401
+ alertCmd
402
+ .command("list")
403
+ .description("List alerts for a domain")
404
+ .argument("<domain-id>", "Domain ID")
405
+ .option("--json", "Output as JSON", false)
406
+ .action((domainId, opts) => {
407
+ const alerts = listAlerts(domainId);
408
+
409
+ if (opts.json) {
410
+ console.log(JSON.stringify(alerts, null, 2));
411
+ } else {
412
+ if (alerts.length === 0) {
413
+ console.log("No alerts set.");
414
+ return;
415
+ }
416
+ for (const a of alerts) {
417
+ const daysBefore = a.trigger_days_before ? ` (${a.trigger_days_before} days before)` : "";
418
+ const sent = a.sent_at ? ` — sent ${a.sent_at}` : "";
419
+ console.log(` ${a.type}${daysBefore}${sent}`);
420
+ }
421
+ }
422
+ });
423
+
424
+ alertCmd
425
+ .command("remove")
426
+ .description("Remove an alert")
427
+ .argument("<id>", "Alert ID")
428
+ .action((id) => {
429
+ const deleted = deleteAlert(id);
430
+ if (deleted) {
431
+ console.log(`Deleted alert ${id}`);
432
+ } else {
433
+ console.error(`Alert '${id}' not found.`);
434
+ process.exit(1);
435
+ }
436
+ });
437
+
438
+ program.parse(process.argv);
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Database connection for microservice-domains
3
+ */
4
+
5
+ import { Database } from "bun:sqlite";
6
+ import { existsSync, mkdirSync } from "node:fs";
7
+ import { dirname, join, resolve } from "node:path";
8
+ import { MIGRATIONS } from "./migrations.js";
9
+
10
+ let _db: Database | null = null;
11
+
12
+ function getDbPath(): string {
13
+ // Environment variable override
14
+ if (process.env["MICROSERVICES_DIR"]) {
15
+ return join(process.env["MICROSERVICES_DIR"], "microservice-domains", "data.db");
16
+ }
17
+
18
+ // Check for .microservices in current or parent directories
19
+ let dir = resolve(process.cwd());
20
+ while (true) {
21
+ const candidate = join(dir, ".microservices", "microservice-domains", "data.db");
22
+ const msDir = join(dir, ".microservices");
23
+ if (existsSync(msDir)) return candidate;
24
+ const parent = dirname(dir);
25
+ if (parent === dir) break;
26
+ dir = parent;
27
+ }
28
+
29
+ // Global fallback
30
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
31
+ return join(home, ".microservices", "microservice-domains", "data.db");
32
+ }
33
+
34
+ function ensureDir(filePath: string): void {
35
+ const dir = dirname(resolve(filePath));
36
+ if (!existsSync(dir)) {
37
+ mkdirSync(dir, { recursive: true });
38
+ }
39
+ }
40
+
41
+ export function getDatabase(): Database {
42
+ if (_db) return _db;
43
+
44
+ const dbPath = getDbPath();
45
+ ensureDir(dbPath);
46
+
47
+ _db = new Database(dbPath);
48
+ _db.exec("PRAGMA journal_mode = WAL");
49
+ _db.exec("PRAGMA foreign_keys = ON");
50
+
51
+ // Create migrations table
52
+ _db.exec(`
53
+ CREATE TABLE IF NOT EXISTS _migrations (
54
+ id INTEGER PRIMARY KEY,
55
+ name TEXT NOT NULL,
56
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
57
+ )
58
+ `);
59
+
60
+ // Apply pending migrations
61
+ const applied = _db
62
+ .query("SELECT id FROM _migrations ORDER BY id")
63
+ .all() as { id: number }[];
64
+ const appliedIds = new Set(applied.map((r) => r.id));
65
+
66
+ for (const migration of MIGRATIONS) {
67
+ if (appliedIds.has(migration.id)) continue;
68
+
69
+ _db.exec("BEGIN");
70
+ try {
71
+ _db.exec(migration.sql);
72
+ _db.prepare("INSERT INTO _migrations (id, name) VALUES (?, ?)").run(
73
+ migration.id,
74
+ migration.name
75
+ );
76
+ _db.exec("COMMIT");
77
+ } catch (error) {
78
+ _db.exec("ROLLBACK");
79
+ throw new Error(
80
+ `Migration ${migration.id} (${migration.name}) failed: ${error instanceof Error ? error.message : String(error)}`
81
+ );
82
+ }
83
+ }
84
+
85
+ return _db;
86
+ }
87
+
88
+ export function closeDatabase(): void {
89
+ if (_db) {
90
+ _db.close();
91
+ _db = null;
92
+ }
93
+ }