@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,416 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from "commander";
4
+ import {
5
+ createProduct,
6
+ getProduct,
7
+ listProducts,
8
+ updateProduct,
9
+ deleteProduct,
10
+ searchProducts,
11
+ getProductWithTiers,
12
+ bulkImportProducts,
13
+ exportProducts,
14
+ getProductStats,
15
+ } from "../db/products.js";
16
+ import {
17
+ createCategory,
18
+ listCategories,
19
+ getCategoryTree,
20
+ } from "../db/categories.js";
21
+ import {
22
+ createPricingTier,
23
+ listPricingTiers,
24
+ deletePricingTier,
25
+ } from "../db/pricing-tiers.js";
26
+
27
+ const program = new Command();
28
+
29
+ program
30
+ .name("microservice-products")
31
+ .description("Product catalog microservice")
32
+ .version("0.0.1");
33
+
34
+ // --- Products ---
35
+
36
+ program
37
+ .command("create")
38
+ .description("Create a new product")
39
+ .requiredOption("--name <name>", "Product name")
40
+ .option("--description <desc>", "Description")
41
+ .option("--type <type>", "Type (product/service/subscription/digital)", "product")
42
+ .option("--sku <sku>", "SKU")
43
+ .option("--price <price>", "Price")
44
+ .option("--currency <currency>", "Currency", "USD")
45
+ .option("--unit <unit>", "Unit of measure")
46
+ .option("--category <category>", "Category name")
47
+ .option("--status <status>", "Status (active/draft/archived)", "draft")
48
+ .option("--json", "Output as JSON", false)
49
+ .action((opts) => {
50
+ const product = createProduct({
51
+ name: opts.name,
52
+ description: opts.description,
53
+ type: opts.type,
54
+ sku: opts.sku,
55
+ price: opts.price ? parseFloat(opts.price) : undefined,
56
+ currency: opts.currency,
57
+ unit: opts.unit,
58
+ category: opts.category,
59
+ status: opts.status,
60
+ });
61
+
62
+ if (opts.json) {
63
+ console.log(JSON.stringify(product, null, 2));
64
+ } else {
65
+ console.log(`Created product: ${product.name} (${product.id})`);
66
+ }
67
+ });
68
+
69
+ program
70
+ .command("get")
71
+ .description("Get a product by ID")
72
+ .argument("<id>", "Product ID")
73
+ .option("--json", "Output as JSON", false)
74
+ .action((id, opts) => {
75
+ const product = getProductWithTiers(id);
76
+ if (!product) {
77
+ console.error(`Product '${id}' not found.`);
78
+ process.exit(1);
79
+ }
80
+
81
+ if (opts.json) {
82
+ console.log(JSON.stringify(product, null, 2));
83
+ } else {
84
+ console.log(`${product.name}`);
85
+ if (product.sku) console.log(` SKU: ${product.sku}`);
86
+ console.log(` Type: ${product.type}`);
87
+ console.log(` Status: ${product.status}`);
88
+ if (product.price !== null) console.log(` Price: ${product.price} ${product.currency}`);
89
+ if (product.unit) console.log(` Unit: ${product.unit}`);
90
+ if (product.category) console.log(` Category: ${product.category}`);
91
+ if (product.description) console.log(` Description: ${product.description}`);
92
+ if (product.pricing_tiers.length > 0) {
93
+ console.log(` Pricing Tiers:`);
94
+ for (const t of product.pricing_tiers) {
95
+ console.log(` - ${t.name}: ${t.price} ${t.currency} (min qty: ${t.min_quantity})`);
96
+ }
97
+ }
98
+ }
99
+ });
100
+
101
+ program
102
+ .command("list")
103
+ .description("List products")
104
+ .option("--search <query>", "Search by name, description, or SKU")
105
+ .option("--category <category>", "Filter by category")
106
+ .option("--type <type>", "Filter by type")
107
+ .option("--status <status>", "Filter by status")
108
+ .option("--limit <n>", "Limit results")
109
+ .option("--json", "Output as JSON", false)
110
+ .action((opts) => {
111
+ const products = listProducts({
112
+ search: opts.search,
113
+ category: opts.category,
114
+ type: opts.type,
115
+ status: opts.status,
116
+ limit: opts.limit ? parseInt(opts.limit) : undefined,
117
+ });
118
+
119
+ if (opts.json) {
120
+ console.log(JSON.stringify(products, null, 2));
121
+ } else {
122
+ if (products.length === 0) {
123
+ console.log("No products found.");
124
+ return;
125
+ }
126
+ for (const p of products) {
127
+ const sku = p.sku ? ` [${p.sku}]` : "";
128
+ const price = p.price !== null ? ` $${p.price}` : "";
129
+ console.log(` ${p.name}${sku}${price} (${p.status})`);
130
+ }
131
+ console.log(`\n${products.length} product(s)`);
132
+ }
133
+ });
134
+
135
+ program
136
+ .command("update")
137
+ .description("Update a product")
138
+ .argument("<id>", "Product ID")
139
+ .option("--name <name>", "Name")
140
+ .option("--description <desc>", "Description")
141
+ .option("--type <type>", "Type")
142
+ .option("--sku <sku>", "SKU")
143
+ .option("--price <price>", "Price")
144
+ .option("--currency <currency>", "Currency")
145
+ .option("--unit <unit>", "Unit")
146
+ .option("--category <category>", "Category")
147
+ .option("--status <status>", "Status")
148
+ .option("--json", "Output as JSON", false)
149
+ .action((id, opts) => {
150
+ const input: Record<string, unknown> = {};
151
+ if (opts.name !== undefined) input.name = opts.name;
152
+ if (opts.description !== undefined) input.description = opts.description;
153
+ if (opts.type !== undefined) input.type = opts.type;
154
+ if (opts.sku !== undefined) input.sku = opts.sku;
155
+ if (opts.price !== undefined) input.price = parseFloat(opts.price);
156
+ if (opts.currency !== undefined) input.currency = opts.currency;
157
+ if (opts.unit !== undefined) input.unit = opts.unit;
158
+ if (opts.category !== undefined) input.category = opts.category;
159
+ if (opts.status !== undefined) input.status = opts.status;
160
+
161
+ const product = updateProduct(id, input);
162
+ if (!product) {
163
+ console.error(`Product '${id}' not found.`);
164
+ process.exit(1);
165
+ }
166
+
167
+ if (opts.json) {
168
+ console.log(JSON.stringify(product, null, 2));
169
+ } else {
170
+ console.log(`Updated: ${product.name}`);
171
+ }
172
+ });
173
+
174
+ program
175
+ .command("delete")
176
+ .description("Delete a product")
177
+ .argument("<id>", "Product ID")
178
+ .action((id) => {
179
+ const deleted = deleteProduct(id);
180
+ if (deleted) {
181
+ console.log(`Deleted product ${id}`);
182
+ } else {
183
+ console.error(`Product '${id}' not found.`);
184
+ process.exit(1);
185
+ }
186
+ });
187
+
188
+ program
189
+ .command("search")
190
+ .description("Search products")
191
+ .argument("<query>", "Search term")
192
+ .option("--json", "Output as JSON", false)
193
+ .action((query, opts) => {
194
+ const results = searchProducts(query);
195
+
196
+ if (opts.json) {
197
+ console.log(JSON.stringify(results, null, 2));
198
+ } else {
199
+ if (results.length === 0) {
200
+ console.log(`No products matching "${query}".`);
201
+ return;
202
+ }
203
+ for (const p of results) {
204
+ const sku = p.sku ? ` [${p.sku}]` : "";
205
+ console.log(` ${p.name}${sku} (${p.status})`);
206
+ }
207
+ }
208
+ });
209
+
210
+ // --- Categories ---
211
+
212
+ const categoryCmd = program
213
+ .command("category")
214
+ .description("Category management");
215
+
216
+ categoryCmd
217
+ .command("create")
218
+ .description("Create a category")
219
+ .requiredOption("--name <name>", "Category name")
220
+ .option("--parent <id>", "Parent category ID")
221
+ .option("--description <desc>", "Description")
222
+ .option("--json", "Output as JSON", false)
223
+ .action((opts) => {
224
+ const category = createCategory({
225
+ name: opts.name,
226
+ parent_id: opts.parent,
227
+ description: opts.description,
228
+ });
229
+
230
+ if (opts.json) {
231
+ console.log(JSON.stringify(category, null, 2));
232
+ } else {
233
+ console.log(`Created category: ${category.name} (${category.id})`);
234
+ }
235
+ });
236
+
237
+ categoryCmd
238
+ .command("list")
239
+ .description("List categories")
240
+ .option("--search <query>", "Search")
241
+ .option("--json", "Output as JSON", false)
242
+ .action((opts) => {
243
+ const categories = listCategories({
244
+ search: opts.search,
245
+ });
246
+
247
+ if (opts.json) {
248
+ console.log(JSON.stringify(categories, null, 2));
249
+ } else {
250
+ if (categories.length === 0) {
251
+ console.log("No categories found.");
252
+ return;
253
+ }
254
+ for (const c of categories) {
255
+ const parent = c.parent_id ? ` (child of ${c.parent_id})` : "";
256
+ console.log(` ${c.name}${parent}`);
257
+ }
258
+ }
259
+ });
260
+
261
+ categoryCmd
262
+ .command("tree")
263
+ .description("Display category tree")
264
+ .option("--json", "Output as JSON", false)
265
+ .action((opts) => {
266
+ const tree = getCategoryTree();
267
+
268
+ if (opts.json) {
269
+ console.log(JSON.stringify(tree, null, 2));
270
+ } else {
271
+ if (tree.length === 0) {
272
+ console.log("No categories found.");
273
+ return;
274
+ }
275
+ function printTree(nodes: typeof tree, indent = 0): void {
276
+ for (const node of nodes) {
277
+ console.log(`${" ".repeat(indent)}${indent > 0 ? "|- " : ""}${node.name}`);
278
+ if (node.children.length > 0) {
279
+ printTree(node.children, indent + 1);
280
+ }
281
+ }
282
+ }
283
+ printTree(tree);
284
+ }
285
+ });
286
+
287
+ // --- Pricing Tiers ---
288
+
289
+ const tierCmd = program
290
+ .command("tier")
291
+ .description("Pricing tier management");
292
+
293
+ tierCmd
294
+ .command("add")
295
+ .description("Add a pricing tier to a product")
296
+ .requiredOption("--product <id>", "Product ID")
297
+ .requiredOption("--name <name>", "Tier name")
298
+ .requiredOption("--min-quantity <n>", "Minimum quantity")
299
+ .requiredOption("--price <price>", "Price")
300
+ .option("--currency <currency>", "Currency", "USD")
301
+ .option("--json", "Output as JSON", false)
302
+ .action((opts) => {
303
+ const tier = createPricingTier({
304
+ product_id: opts.product,
305
+ name: opts.name,
306
+ min_quantity: parseInt(opts.minQuantity),
307
+ price: parseFloat(opts.price),
308
+ currency: opts.currency,
309
+ });
310
+
311
+ if (opts.json) {
312
+ console.log(JSON.stringify(tier, null, 2));
313
+ } else {
314
+ console.log(`Created tier: ${tier.name} (${tier.id})`);
315
+ }
316
+ });
317
+
318
+ tierCmd
319
+ .command("list")
320
+ .description("List pricing tiers for a product")
321
+ .argument("<product-id>", "Product ID")
322
+ .option("--json", "Output as JSON", false)
323
+ .action((productId, opts) => {
324
+ const tiers = listPricingTiers(productId);
325
+
326
+ if (opts.json) {
327
+ console.log(JSON.stringify(tiers, null, 2));
328
+ } else {
329
+ if (tiers.length === 0) {
330
+ console.log("No pricing tiers found.");
331
+ return;
332
+ }
333
+ for (const t of tiers) {
334
+ console.log(` ${t.name}: ${t.price} ${t.currency} (min qty: ${t.min_quantity})`);
335
+ }
336
+ }
337
+ });
338
+
339
+ tierCmd
340
+ .command("remove")
341
+ .description("Remove a pricing tier")
342
+ .argument("<id>", "Tier ID")
343
+ .action((id) => {
344
+ const deleted = deletePricingTier(id);
345
+ if (deleted) {
346
+ console.log(`Deleted tier ${id}`);
347
+ } else {
348
+ console.error(`Tier '${id}' not found.`);
349
+ process.exit(1);
350
+ }
351
+ });
352
+
353
+ // --- Import/Export ---
354
+
355
+ program
356
+ .command("import")
357
+ .description("Bulk import products from CSV")
358
+ .argument("<csv>", "CSV string")
359
+ .option("--json", "Output as JSON", false)
360
+ .action((csv, opts) => {
361
+ const result = bulkImportProducts(csv);
362
+
363
+ if (opts.json) {
364
+ console.log(JSON.stringify(result, null, 2));
365
+ } else {
366
+ console.log(`Imported: ${result.imported}, Skipped: ${result.skipped}`);
367
+ if (result.errors.length > 0) {
368
+ console.log("Errors:");
369
+ for (const e of result.errors) {
370
+ console.log(` - ${e}`);
371
+ }
372
+ }
373
+ }
374
+ });
375
+
376
+ program
377
+ .command("export")
378
+ .description("Export products")
379
+ .option("--format <format>", "Output format (csv/json)", "json")
380
+ .action((opts) => {
381
+ const output = exportProducts(opts.format);
382
+ console.log(output);
383
+ });
384
+
385
+ // --- Stats ---
386
+
387
+ program
388
+ .command("stats")
389
+ .description("Show product statistics")
390
+ .option("--json", "Output as JSON", false)
391
+ .action((opts) => {
392
+ const stats = getProductStats();
393
+
394
+ if (opts.json) {
395
+ console.log(JSON.stringify(stats, null, 2));
396
+ } else {
397
+ console.log(`Total products: ${stats.total}`);
398
+ console.log("\nBy status:");
399
+ for (const [k, v] of Object.entries(stats.by_status)) {
400
+ console.log(` ${k}: ${v}`);
401
+ }
402
+ console.log("\nBy type:");
403
+ for (const [k, v] of Object.entries(stats.by_type)) {
404
+ console.log(` ${k}: ${v}`);
405
+ }
406
+ console.log("\nBy category:");
407
+ for (const [k, v] of Object.entries(stats.by_category)) {
408
+ console.log(` ${k}: ${v}`);
409
+ }
410
+ if (stats.avg_price !== null) {
411
+ console.log(`\nPrice range: ${stats.min_price} - ${stats.max_price} (avg: ${stats.avg_price?.toFixed(2)})`);
412
+ }
413
+ }
414
+ });
415
+
416
+ program.parse(process.argv);
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Category CRUD operations
3
+ */
4
+
5
+ import { getDatabase } from "./database.js";
6
+
7
+ export interface Category {
8
+ id: string;
9
+ name: string;
10
+ parent_id: string | null;
11
+ description: string | null;
12
+ created_at: string;
13
+ }
14
+
15
+ export interface CreateCategoryInput {
16
+ name: string;
17
+ parent_id?: string;
18
+ description?: string;
19
+ }
20
+
21
+ export function createCategory(input: CreateCategoryInput): Category {
22
+ const db = getDatabase();
23
+ const id = crypto.randomUUID();
24
+
25
+ db.prepare(
26
+ `INSERT INTO categories (id, name, parent_id, description)
27
+ VALUES (?, ?, ?, ?)`
28
+ ).run(id, input.name, input.parent_id || null, input.description || null);
29
+
30
+ return getCategory(id)!;
31
+ }
32
+
33
+ export function getCategory(id: string): Category | null {
34
+ const db = getDatabase();
35
+ return db.prepare("SELECT * FROM categories WHERE id = ?").get(id) as Category | null;
36
+ }
37
+
38
+ export interface ListCategoriesOptions {
39
+ search?: string;
40
+ parent_id?: string | null;
41
+ limit?: number;
42
+ offset?: number;
43
+ }
44
+
45
+ export function listCategories(options: ListCategoriesOptions = {}): Category[] {
46
+ const db = getDatabase();
47
+ const conditions: string[] = [];
48
+ const params: unknown[] = [];
49
+
50
+ if (options.search) {
51
+ conditions.push("(name LIKE ? OR description LIKE ?)");
52
+ const q = `%${options.search}%`;
53
+ params.push(q, q);
54
+ }
55
+
56
+ if (options.parent_id !== undefined) {
57
+ if (options.parent_id === null) {
58
+ conditions.push("parent_id IS NULL");
59
+ } else {
60
+ conditions.push("parent_id = ?");
61
+ params.push(options.parent_id);
62
+ }
63
+ }
64
+
65
+ let sql = "SELECT * FROM categories";
66
+ if (conditions.length > 0) {
67
+ sql += " WHERE " + conditions.join(" AND ");
68
+ }
69
+ sql += " ORDER BY name";
70
+
71
+ if (options.limit) {
72
+ sql += " LIMIT ?";
73
+ params.push(options.limit);
74
+ }
75
+ if (options.offset) {
76
+ sql += " OFFSET ?";
77
+ params.push(options.offset);
78
+ }
79
+
80
+ return db.prepare(sql).all(...params) as Category[];
81
+ }
82
+
83
+ export interface UpdateCategoryInput {
84
+ name?: string;
85
+ parent_id?: string | null;
86
+ description?: string;
87
+ }
88
+
89
+ export function updateCategory(
90
+ id: string,
91
+ input: UpdateCategoryInput
92
+ ): Category | null {
93
+ const db = getDatabase();
94
+ const existing = getCategory(id);
95
+ if (!existing) return null;
96
+
97
+ const sets: string[] = [];
98
+ const params: unknown[] = [];
99
+
100
+ if (input.name !== undefined) {
101
+ sets.push("name = ?");
102
+ params.push(input.name);
103
+ }
104
+ if (input.parent_id !== undefined) {
105
+ sets.push("parent_id = ?");
106
+ params.push(input.parent_id);
107
+ }
108
+ if (input.description !== undefined) {
109
+ sets.push("description = ?");
110
+ params.push(input.description);
111
+ }
112
+
113
+ if (sets.length === 0) return existing;
114
+
115
+ params.push(id);
116
+
117
+ db.prepare(
118
+ `UPDATE categories SET ${sets.join(", ")} WHERE id = ?`
119
+ ).run(...params);
120
+
121
+ return getCategory(id);
122
+ }
123
+
124
+ export function deleteCategory(id: string): boolean {
125
+ const db = getDatabase();
126
+ const result = db.prepare("DELETE FROM categories WHERE id = ?").run(id);
127
+ return result.changes > 0;
128
+ }
129
+
130
+ export interface CategoryTreeNode extends Category {
131
+ children: CategoryTreeNode[];
132
+ }
133
+
134
+ export function getCategoryTree(): CategoryTreeNode[] {
135
+ const all = listCategories();
136
+ const byId = new Map<string, CategoryTreeNode>();
137
+ const roots: CategoryTreeNode[] = [];
138
+
139
+ // Create nodes
140
+ for (const cat of all) {
141
+ byId.set(cat.id, { ...cat, children: [] });
142
+ }
143
+
144
+ // Build tree
145
+ for (const node of byId.values()) {
146
+ if (node.parent_id && byId.has(node.parent_id)) {
147
+ byId.get(node.parent_id)!.children.push(node);
148
+ } else {
149
+ roots.push(node);
150
+ }
151
+ }
152
+
153
+ return roots;
154
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Database connection for microservice-products
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-products", "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-products", "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-products", "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
+ }