@hasna/microservices 0.0.9 → 0.0.11

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 (100) hide show
  1. package/bin/index.js +236 -36
  2. package/bin/mcp.js +153 -4
  3. package/dist/index.js +120 -3
  4. package/microservices/microservice-analytics/package.json +27 -0
  5. package/microservices/microservice-analytics/src/cli/index.ts +373 -0
  6. package/microservices/microservice-analytics/src/db/analytics.ts +564 -0
  7. package/microservices/microservice-analytics/src/db/database.ts +93 -0
  8. package/microservices/microservice-analytics/src/db/migrations.ts +50 -0
  9. package/microservices/microservice-analytics/src/index.ts +37 -0
  10. package/microservices/microservice-analytics/src/mcp/index.ts +334 -0
  11. package/microservices/microservice-assets/package.json +27 -0
  12. package/microservices/microservice-assets/src/cli/index.ts +375 -0
  13. package/microservices/microservice-assets/src/db/assets.ts +370 -0
  14. package/microservices/microservice-assets/src/db/database.ts +93 -0
  15. package/microservices/microservice-assets/src/db/migrations.ts +51 -0
  16. package/microservices/microservice-assets/src/index.ts +32 -0
  17. package/microservices/microservice-assets/src/mcp/index.ts +346 -0
  18. package/microservices/microservice-compliance/package.json +27 -0
  19. package/microservices/microservice-compliance/src/cli/index.ts +467 -0
  20. package/microservices/microservice-compliance/src/db/compliance.ts +633 -0
  21. package/microservices/microservice-compliance/src/db/database.ts +93 -0
  22. package/microservices/microservice-compliance/src/db/migrations.ts +63 -0
  23. package/microservices/microservice-compliance/src/index.ts +46 -0
  24. package/microservices/microservice-compliance/src/mcp/index.ts +438 -0
  25. package/microservices/microservice-habits/package.json +27 -0
  26. package/microservices/microservice-habits/src/cli/index.ts +315 -0
  27. package/microservices/microservice-habits/src/db/database.ts +93 -0
  28. package/microservices/microservice-habits/src/db/habits.ts +451 -0
  29. package/microservices/microservice-habits/src/db/migrations.ts +46 -0
  30. package/microservices/microservice-habits/src/index.ts +31 -0
  31. package/microservices/microservice-habits/src/mcp/index.ts +313 -0
  32. package/microservices/microservice-health/package.json +27 -0
  33. package/microservices/microservice-health/src/cli/index.ts +484 -0
  34. package/microservices/microservice-health/src/db/database.ts +93 -0
  35. package/microservices/microservice-health/src/db/health.ts +708 -0
  36. package/microservices/microservice-health/src/db/migrations.ts +70 -0
  37. package/microservices/microservice-health/src/index.ts +63 -0
  38. package/microservices/microservice-health/src/mcp/index.ts +437 -0
  39. package/microservices/microservice-leads/package.json +27 -0
  40. package/microservices/microservice-leads/src/cli/index.ts +596 -0
  41. package/microservices/microservice-leads/src/db/database.ts +93 -0
  42. package/microservices/microservice-leads/src/db/leads.ts +520 -0
  43. package/microservices/microservice-leads/src/db/lists.ts +151 -0
  44. package/microservices/microservice-leads/src/db/migrations.ts +93 -0
  45. package/microservices/microservice-leads/src/index.ts +65 -0
  46. package/microservices/microservice-leads/src/lib/enrichment.ts +202 -0
  47. package/microservices/microservice-leads/src/lib/scoring.ts +134 -0
  48. package/microservices/microservice-leads/src/mcp/index.ts +533 -0
  49. package/microservices/microservice-notifications/package.json +27 -0
  50. package/microservices/microservice-notifications/src/cli/index.ts +349 -0
  51. package/microservices/microservice-notifications/src/db/database.ts +93 -0
  52. package/microservices/microservice-notifications/src/db/migrations.ts +62 -0
  53. package/microservices/microservice-notifications/src/db/notifications.ts +509 -0
  54. package/microservices/microservice-notifications/src/index.ts +41 -0
  55. package/microservices/microservice-notifications/src/mcp/index.ts +422 -0
  56. package/microservices/microservice-products/package.json +27 -0
  57. package/microservices/microservice-products/src/cli/index.ts +416 -0
  58. package/microservices/microservice-products/src/db/categories.ts +154 -0
  59. package/microservices/microservice-products/src/db/database.ts +93 -0
  60. package/microservices/microservice-products/src/db/migrations.ts +58 -0
  61. package/microservices/microservice-products/src/db/pricing-tiers.ts +66 -0
  62. package/microservices/microservice-products/src/db/products.ts +452 -0
  63. package/microservices/microservice-products/src/index.ts +53 -0
  64. package/microservices/microservice-products/src/mcp/index.ts +453 -0
  65. package/microservices/microservice-projects/package.json +27 -0
  66. package/microservices/microservice-projects/src/cli/index.ts +480 -0
  67. package/microservices/microservice-projects/src/db/database.ts +93 -0
  68. package/microservices/microservice-projects/src/db/migrations.ts +65 -0
  69. package/microservices/microservice-projects/src/db/projects.ts +715 -0
  70. package/microservices/microservice-projects/src/index.ts +57 -0
  71. package/microservices/microservice-projects/src/mcp/index.ts +501 -0
  72. package/microservices/microservice-proposals/package.json +27 -0
  73. package/microservices/microservice-proposals/src/cli/index.ts +400 -0
  74. package/microservices/microservice-proposals/src/db/database.ts +93 -0
  75. package/microservices/microservice-proposals/src/db/migrations.ts +52 -0
  76. package/microservices/microservice-proposals/src/db/proposals.ts +532 -0
  77. package/microservices/microservice-proposals/src/index.ts +37 -0
  78. package/microservices/microservice-proposals/src/mcp/index.ts +375 -0
  79. package/microservices/microservice-reading/package.json +27 -0
  80. package/microservices/microservice-reading/src/cli/index.ts +464 -0
  81. package/microservices/microservice-reading/src/db/database.ts +93 -0
  82. package/microservices/microservice-reading/src/db/migrations.ts +59 -0
  83. package/microservices/microservice-reading/src/db/reading.ts +524 -0
  84. package/microservices/microservice-reading/src/index.ts +51 -0
  85. package/microservices/microservice-reading/src/mcp/index.ts +368 -0
  86. package/microservices/microservice-travel/package.json +27 -0
  87. package/microservices/microservice-travel/src/cli/index.ts +505 -0
  88. package/microservices/microservice-travel/src/db/database.ts +93 -0
  89. package/microservices/microservice-travel/src/db/migrations.ts +77 -0
  90. package/microservices/microservice-travel/src/db/travel.ts +802 -0
  91. package/microservices/microservice-travel/src/index.ts +60 -0
  92. package/microservices/microservice-travel/src/mcp/index.ts +495 -0
  93. package/microservices/microservice-wiki/package.json +27 -0
  94. package/microservices/microservice-wiki/src/cli/index.ts +345 -0
  95. package/microservices/microservice-wiki/src/db/database.ts +93 -0
  96. package/microservices/microservice-wiki/src/db/migrations.ts +55 -0
  97. package/microservices/microservice-wiki/src/db/wiki.ts +395 -0
  98. package/microservices/microservice-wiki/src/index.ts +32 -0
  99. package/microservices/microservice-wiki/src/mcp/index.ts +344 -0
  100. package/package.json +1 -1
@@ -0,0 +1,400 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from "commander";
4
+ import {
5
+ createProposal,
6
+ getProposal,
7
+ listProposals,
8
+ updateProposal,
9
+ deleteProposal,
10
+ sendProposal,
11
+ acceptProposal,
12
+ declineProposal,
13
+ convertToInvoice,
14
+ listExpiring,
15
+ getProposalStats,
16
+ searchProposals,
17
+ createTemplate,
18
+ listTemplates,
19
+ useTemplate,
20
+ } from "../db/proposals.js";
21
+
22
+ const program = new Command();
23
+
24
+ program
25
+ .name("microservice-proposals")
26
+ .description("Proposal management microservice")
27
+ .version("0.0.1");
28
+
29
+ // --- Proposals ---
30
+
31
+ program
32
+ .command("create")
33
+ .description("Create a new proposal")
34
+ .requiredOption("--title <title>", "Proposal title")
35
+ .requiredOption("--client-name <name>", "Client name")
36
+ .option("--client-email <email>", "Client email")
37
+ .option("--items <json>", "Items as JSON array")
38
+ .option("--tax-rate <rate>", "Tax rate percentage")
39
+ .option("--discount <amount>", "Discount amount")
40
+ .option("--currency <code>", "Currency code", "USD")
41
+ .option("--valid-until <date>", "Valid until date (YYYY-MM-DD)")
42
+ .option("--notes <notes>", "Notes")
43
+ .option("--terms <terms>", "Terms and conditions")
44
+ .option("--json", "Output as JSON", false)
45
+ .action((opts) => {
46
+ const proposal = createProposal({
47
+ title: opts.title,
48
+ client_name: opts.clientName,
49
+ client_email: opts.clientEmail,
50
+ items: opts.items ? JSON.parse(opts.items) : undefined,
51
+ tax_rate: opts.taxRate ? parseFloat(opts.taxRate) : undefined,
52
+ discount: opts.discount ? parseFloat(opts.discount) : undefined,
53
+ currency: opts.currency,
54
+ valid_until: opts.validUntil,
55
+ notes: opts.notes,
56
+ terms: opts.terms,
57
+ });
58
+
59
+ if (opts.json) {
60
+ console.log(JSON.stringify(proposal, null, 2));
61
+ } else {
62
+ console.log(`Created proposal: ${proposal.title} (${proposal.id})`);
63
+ }
64
+ });
65
+
66
+ program
67
+ .command("get")
68
+ .description("Get a proposal by ID")
69
+ .argument("<id>", "Proposal ID")
70
+ .option("--json", "Output as JSON", false)
71
+ .action((id, opts) => {
72
+ const proposal = getProposal(id);
73
+ if (!proposal) {
74
+ console.error(`Proposal '${id}' not found.`);
75
+ process.exit(1);
76
+ }
77
+
78
+ if (opts.json) {
79
+ console.log(JSON.stringify(proposal, null, 2));
80
+ } else {
81
+ console.log(`${proposal.title}`);
82
+ console.log(` Client: ${proposal.client_name}${proposal.client_email ? ` <${proposal.client_email}>` : ""}`);
83
+ console.log(` Status: ${proposal.status}`);
84
+ console.log(` Total: ${proposal.currency} ${proposal.total.toFixed(2)}`);
85
+ if (proposal.valid_until) console.log(` Valid until: ${proposal.valid_until}`);
86
+ if (proposal.notes) console.log(` Notes: ${proposal.notes}`);
87
+ }
88
+ });
89
+
90
+ program
91
+ .command("list")
92
+ .description("List proposals")
93
+ .option("--status <status>", "Filter by status")
94
+ .option("--client <name>", "Filter by client name")
95
+ .option("--search <query>", "Search proposals")
96
+ .option("--limit <n>", "Limit results")
97
+ .option("--json", "Output as JSON", false)
98
+ .action((opts) => {
99
+ const proposals = listProposals({
100
+ status: opts.status,
101
+ client_name: opts.client,
102
+ search: opts.search,
103
+ limit: opts.limit ? parseInt(opts.limit) : undefined,
104
+ });
105
+
106
+ if (opts.json) {
107
+ console.log(JSON.stringify(proposals, null, 2));
108
+ } else {
109
+ if (proposals.length === 0) {
110
+ console.log("No proposals found.");
111
+ return;
112
+ }
113
+ for (const p of proposals) {
114
+ const status = `[${p.status}]`;
115
+ console.log(` ${p.title} - ${p.client_name} ${status} ${p.currency} ${p.total.toFixed(2)}`);
116
+ }
117
+ console.log(`\n${proposals.length} proposal(s)`);
118
+ }
119
+ });
120
+
121
+ program
122
+ .command("update")
123
+ .description("Update a proposal")
124
+ .argument("<id>", "Proposal ID")
125
+ .option("--title <title>", "Title")
126
+ .option("--client-name <name>", "Client name")
127
+ .option("--client-email <email>", "Client email")
128
+ .option("--items <json>", "Items as JSON array")
129
+ .option("--tax-rate <rate>", "Tax rate")
130
+ .option("--discount <amount>", "Discount")
131
+ .option("--currency <code>", "Currency code")
132
+ .option("--valid-until <date>", "Valid until date")
133
+ .option("--notes <notes>", "Notes")
134
+ .option("--terms <terms>", "Terms")
135
+ .option("--json", "Output as JSON", false)
136
+ .action((id, opts) => {
137
+ const input: Record<string, unknown> = {};
138
+ if (opts.title !== undefined) input.title = opts.title;
139
+ if (opts.clientName !== undefined) input.client_name = opts.clientName;
140
+ if (opts.clientEmail !== undefined) input.client_email = opts.clientEmail;
141
+ if (opts.items !== undefined) input.items = JSON.parse(opts.items);
142
+ if (opts.taxRate !== undefined) input.tax_rate = parseFloat(opts.taxRate);
143
+ if (opts.discount !== undefined) input.discount = parseFloat(opts.discount);
144
+ if (opts.currency !== undefined) input.currency = opts.currency;
145
+ if (opts.validUntil !== undefined) input.valid_until = opts.validUntil;
146
+ if (opts.notes !== undefined) input.notes = opts.notes;
147
+ if (opts.terms !== undefined) input.terms = opts.terms;
148
+
149
+ const proposal = updateProposal(id, input);
150
+ if (!proposal) {
151
+ console.error(`Proposal '${id}' not found.`);
152
+ process.exit(1);
153
+ }
154
+
155
+ if (opts.json) {
156
+ console.log(JSON.stringify(proposal, null, 2));
157
+ } else {
158
+ console.log(`Updated: ${proposal.title}`);
159
+ }
160
+ });
161
+
162
+ program
163
+ .command("delete")
164
+ .description("Delete a proposal")
165
+ .argument("<id>", "Proposal ID")
166
+ .action((id) => {
167
+ const deleted = deleteProposal(id);
168
+ if (deleted) {
169
+ console.log(`Deleted proposal ${id}`);
170
+ } else {
171
+ console.error(`Proposal '${id}' not found.`);
172
+ process.exit(1);
173
+ }
174
+ });
175
+
176
+ program
177
+ .command("send")
178
+ .description("Send a proposal (sets status to sent)")
179
+ .argument("<id>", "Proposal ID")
180
+ .option("--json", "Output as JSON", false)
181
+ .action((id, opts) => {
182
+ const proposal = sendProposal(id);
183
+ if (!proposal) {
184
+ console.error(`Proposal '${id}' not found.`);
185
+ process.exit(1);
186
+ }
187
+
188
+ if (opts.json) {
189
+ console.log(JSON.stringify(proposal, null, 2));
190
+ } else {
191
+ console.log(`Sent proposal: ${proposal.title} to ${proposal.client_name}`);
192
+ }
193
+ });
194
+
195
+ program
196
+ .command("accept")
197
+ .description("Accept a proposal")
198
+ .argument("<id>", "Proposal ID")
199
+ .option("--json", "Output as JSON", false)
200
+ .action((id, opts) => {
201
+ const proposal = acceptProposal(id);
202
+ if (!proposal) {
203
+ console.error(`Proposal '${id}' not found.`);
204
+ process.exit(1);
205
+ }
206
+
207
+ if (opts.json) {
208
+ console.log(JSON.stringify(proposal, null, 2));
209
+ } else {
210
+ console.log(`Accepted proposal: ${proposal.title}`);
211
+ }
212
+ });
213
+
214
+ program
215
+ .command("decline")
216
+ .description("Decline a proposal")
217
+ .argument("<id>", "Proposal ID")
218
+ .option("--reason <reason>", "Decline reason")
219
+ .option("--json", "Output as JSON", false)
220
+ .action((id, opts) => {
221
+ const proposal = declineProposal(id, opts.reason);
222
+ if (!proposal) {
223
+ console.error(`Proposal '${id}' not found.`);
224
+ process.exit(1);
225
+ }
226
+
227
+ if (opts.json) {
228
+ console.log(JSON.stringify(proposal, null, 2));
229
+ } else {
230
+ console.log(`Declined proposal: ${proposal.title}`);
231
+ }
232
+ });
233
+
234
+ program
235
+ .command("convert")
236
+ .description("Convert an accepted proposal to invoice data")
237
+ .argument("<id>", "Proposal ID")
238
+ .option("--json", "Output as JSON", false)
239
+ .action((id, opts) => {
240
+ const invoiceData = convertToInvoice(id);
241
+ if (!invoiceData) {
242
+ console.error(`Proposal '${id}' not found.`);
243
+ process.exit(1);
244
+ }
245
+
246
+ if (opts.json) {
247
+ console.log(JSON.stringify(invoiceData, null, 2));
248
+ } else {
249
+ console.log(`Invoice data for: ${invoiceData.client_name}`);
250
+ console.log(` Total: ${invoiceData.currency} ${invoiceData.total.toFixed(2)}`);
251
+ console.log(` Items: ${invoiceData.items.length}`);
252
+ console.log(` Proposal ID: ${invoiceData.proposal_id}`);
253
+ }
254
+ });
255
+
256
+ program
257
+ .command("search")
258
+ .description("Search proposals")
259
+ .argument("<query>", "Search term")
260
+ .option("--json", "Output as JSON", false)
261
+ .action((query, opts) => {
262
+ const results = searchProposals(query);
263
+
264
+ if (opts.json) {
265
+ console.log(JSON.stringify(results, null, 2));
266
+ } else {
267
+ if (results.length === 0) {
268
+ console.log(`No proposals matching "${query}".`);
269
+ return;
270
+ }
271
+ for (const p of results) {
272
+ console.log(` ${p.title} - ${p.client_name} [${p.status}]`);
273
+ }
274
+ }
275
+ });
276
+
277
+ program
278
+ .command("expiring")
279
+ .description("List proposals expiring within N days")
280
+ .option("--days <n>", "Number of days", "30")
281
+ .option("--json", "Output as JSON", false)
282
+ .action((opts) => {
283
+ const days = parseInt(opts.days);
284
+ const proposals = listExpiring(days);
285
+
286
+ if (opts.json) {
287
+ console.log(JSON.stringify(proposals, null, 2));
288
+ } else {
289
+ if (proposals.length === 0) {
290
+ console.log(`No proposals expiring within ${days} days.`);
291
+ return;
292
+ }
293
+ for (const p of proposals) {
294
+ console.log(` ${p.title} - ${p.client_name} (expires: ${p.valid_until})`);
295
+ }
296
+ }
297
+ });
298
+
299
+ program
300
+ .command("stats")
301
+ .description("Get proposal statistics")
302
+ .option("--json", "Output as JSON", false)
303
+ .action((opts) => {
304
+ const stats = getProposalStats();
305
+
306
+ if (opts.json) {
307
+ console.log(JSON.stringify(stats, null, 2));
308
+ } else {
309
+ console.log(`Proposal Statistics:`);
310
+ console.log(` Total: ${stats.total}`);
311
+ console.log(` Total Value: $${stats.total_value.toFixed(2)}`);
312
+ console.log(` Average Value: $${stats.average_value.toFixed(2)}`);
313
+ console.log(` Conversion Rate: ${stats.conversion_rate.toFixed(1)}%`);
314
+ console.log(` Accepted Value: $${stats.accepted_value.toFixed(2)}`);
315
+ console.log(` By Status:`);
316
+ for (const [status, count] of Object.entries(stats.by_status)) {
317
+ console.log(` ${status}: ${count}`);
318
+ }
319
+ }
320
+ });
321
+
322
+ // --- Templates ---
323
+
324
+ const templateCmd = program
325
+ .command("template")
326
+ .description("Proposal template management");
327
+
328
+ templateCmd
329
+ .command("create")
330
+ .description("Create a proposal template")
331
+ .requiredOption("--name <name>", "Template name")
332
+ .option("--items <json>", "Items as JSON array")
333
+ .option("--terms <terms>", "Default terms")
334
+ .option("--notes <notes>", "Default notes")
335
+ .option("--json", "Output as JSON", false)
336
+ .action((opts) => {
337
+ const template = createTemplate({
338
+ name: opts.name,
339
+ items: opts.items ? JSON.parse(opts.items) : undefined,
340
+ terms: opts.terms,
341
+ notes: opts.notes,
342
+ });
343
+
344
+ if (opts.json) {
345
+ console.log(JSON.stringify(template, null, 2));
346
+ } else {
347
+ console.log(`Created template: ${template.name} (${template.id})`);
348
+ }
349
+ });
350
+
351
+ templateCmd
352
+ .command("list")
353
+ .description("List all templates")
354
+ .option("--json", "Output as JSON", false)
355
+ .action((opts) => {
356
+ const templates = listTemplates();
357
+
358
+ if (opts.json) {
359
+ console.log(JSON.stringify(templates, null, 2));
360
+ } else {
361
+ if (templates.length === 0) {
362
+ console.log("No templates found.");
363
+ return;
364
+ }
365
+ for (const t of templates) {
366
+ console.log(` ${t.name} (${t.items.length} items) — ${t.id}`);
367
+ }
368
+ }
369
+ });
370
+
371
+ templateCmd
372
+ .command("use")
373
+ .description("Create a proposal from a template")
374
+ .argument("<template-id>", "Template ID")
375
+ .requiredOption("--title <title>", "Proposal title")
376
+ .requiredOption("--client-name <name>", "Client name")
377
+ .option("--client-email <email>", "Client email")
378
+ .option("--valid-until <date>", "Valid until date")
379
+ .option("--json", "Output as JSON", false)
380
+ .action((templateId, opts) => {
381
+ const proposal = useTemplate(templateId, {
382
+ title: opts.title,
383
+ client_name: opts.clientName,
384
+ client_email: opts.clientEmail,
385
+ valid_until: opts.validUntil,
386
+ });
387
+
388
+ if (!proposal) {
389
+ console.error(`Template '${templateId}' not found.`);
390
+ process.exit(1);
391
+ }
392
+
393
+ if (opts.json) {
394
+ console.log(JSON.stringify(proposal, null, 2));
395
+ } else {
396
+ console.log(`Created proposal from template: ${proposal.title} (${proposal.id})`);
397
+ }
398
+ });
399
+
400
+ program.parse(process.argv);
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Database connection for microservice-proposals
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-proposals", "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-proposals", "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-proposals", "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
+ }
@@ -0,0 +1,52 @@
1
+ export interface MigrationEntry {
2
+ id: number;
3
+ name: string;
4
+ sql: string;
5
+ }
6
+
7
+ export const MIGRATIONS: MigrationEntry[] = [
8
+ {
9
+ id: 1,
10
+ name: "initial_schema",
11
+ sql: `
12
+ CREATE TABLE IF NOT EXISTS proposals (
13
+ id TEXT PRIMARY KEY,
14
+ title TEXT NOT NULL,
15
+ client_name TEXT NOT NULL,
16
+ client_email TEXT,
17
+ status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'sent', 'viewed', 'accepted', 'declined', 'expired')),
18
+ items TEXT NOT NULL DEFAULT '[]',
19
+ subtotal REAL NOT NULL DEFAULT 0,
20
+ tax_rate REAL NOT NULL DEFAULT 0,
21
+ tax_amount REAL NOT NULL DEFAULT 0,
22
+ discount REAL NOT NULL DEFAULT 0,
23
+ total REAL NOT NULL DEFAULT 0,
24
+ currency TEXT NOT NULL DEFAULT 'USD',
25
+ valid_until TEXT,
26
+ notes TEXT,
27
+ terms TEXT,
28
+ sent_at TEXT,
29
+ viewed_at TEXT,
30
+ responded_at TEXT,
31
+ metadata TEXT NOT NULL DEFAULT '{}',
32
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
33
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
34
+ );
35
+
36
+ CREATE TABLE IF NOT EXISTS proposal_templates (
37
+ id TEXT PRIMARY KEY,
38
+ name TEXT NOT NULL,
39
+ items TEXT NOT NULL DEFAULT '[]',
40
+ terms TEXT,
41
+ notes TEXT,
42
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
43
+ );
44
+
45
+ CREATE INDEX IF NOT EXISTS idx_proposals_status ON proposals(status);
46
+ CREATE INDEX IF NOT EXISTS idx_proposals_client_name ON proposals(client_name);
47
+ CREATE INDEX IF NOT EXISTS idx_proposals_client_email ON proposals(client_email);
48
+ CREATE INDEX IF NOT EXISTS idx_proposals_valid_until ON proposals(valid_until);
49
+ CREATE INDEX IF NOT EXISTS idx_proposal_templates_name ON proposal_templates(name);
50
+ `,
51
+ },
52
+ ];