@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,345 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from "commander";
4
+ import {
5
+ createPage,
6
+ getPage,
7
+ getPageBySlug,
8
+ listPages,
9
+ updatePage,
10
+ deletePage,
11
+ searchPages,
12
+ getPageTree,
13
+ getRecentlyUpdated,
14
+ getByCategory,
15
+ getByTag,
16
+ getPageHistory,
17
+ revertToVersion,
18
+ type PageTreeNode,
19
+ } from "../db/wiki.js";
20
+
21
+ const program = new Command();
22
+
23
+ program
24
+ .name("microservice-wiki")
25
+ .description("Wiki microservice")
26
+ .version("0.0.1");
27
+
28
+ // --- Pages ---
29
+
30
+ const pageCmd = program
31
+ .command("page")
32
+ .description("Page management");
33
+
34
+ pageCmd
35
+ .command("create")
36
+ .description("Create a new page")
37
+ .requiredOption("--title <title>", "Page title")
38
+ .option("--slug <slug>", "URL slug (auto-generated from title if omitted)")
39
+ .option("--content <content>", "Page content")
40
+ .option("--format <format>", "Content format (markdown or html)", "markdown")
41
+ .option("--category <category>", "Category")
42
+ .option("--parent <id>", "Parent page ID")
43
+ .option("--author <author>", "Author name")
44
+ .option("--status <status>", "Status (draft, published, archived)", "published")
45
+ .option("--tags <tags>", "Comma-separated tags")
46
+ .option("--json", "Output as JSON", false)
47
+ .action((opts) => {
48
+ const page = createPage({
49
+ title: opts.title,
50
+ slug: opts.slug,
51
+ content: opts.content,
52
+ format: opts.format,
53
+ category: opts.category,
54
+ parent_id: opts.parent,
55
+ author: opts.author,
56
+ status: opts.status,
57
+ tags: opts.tags ? opts.tags.split(",").map((t: string) => t.trim()) : undefined,
58
+ });
59
+
60
+ if (opts.json) {
61
+ console.log(JSON.stringify(page, null, 2));
62
+ } else {
63
+ console.log(`Created page: ${page.title} (${page.id})`);
64
+ console.log(` Slug: ${page.slug}`);
65
+ }
66
+ });
67
+
68
+ pageCmd
69
+ .command("get")
70
+ .description("Get a page by ID or slug")
71
+ .argument("<id-or-slug>", "Page ID or slug")
72
+ .option("--json", "Output as JSON", false)
73
+ .action((idOrSlug, opts) => {
74
+ let page = getPage(idOrSlug);
75
+ if (!page) page = getPageBySlug(idOrSlug);
76
+ if (!page) {
77
+ console.error(`Page '${idOrSlug}' not found.`);
78
+ process.exit(1);
79
+ }
80
+
81
+ if (opts.json) {
82
+ console.log(JSON.stringify(page, null, 2));
83
+ } else {
84
+ console.log(`${page.title} (v${page.version})`);
85
+ console.log(` Slug: ${page.slug}`);
86
+ console.log(` Status: ${page.status}`);
87
+ console.log(` Format: ${page.format}`);
88
+ if (page.category) console.log(` Category: ${page.category}`);
89
+ if (page.author) console.log(` Author: ${page.author}`);
90
+ if (page.tags.length) console.log(` Tags: ${page.tags.join(", ")}`);
91
+ if (page.content) console.log(`\n${page.content}`);
92
+ }
93
+ });
94
+
95
+ pageCmd
96
+ .command("list")
97
+ .description("List pages")
98
+ .option("--search <query>", "Search by title or content")
99
+ .option("--category <category>", "Filter by category")
100
+ .option("--status <status>", "Filter by status")
101
+ .option("--tag <tag>", "Filter by tag")
102
+ .option("--limit <n>", "Limit results")
103
+ .option("--json", "Output as JSON", false)
104
+ .action((opts) => {
105
+ const pages = listPages({
106
+ search: opts.search,
107
+ category: opts.category,
108
+ status: opts.status,
109
+ tag: opts.tag,
110
+ limit: opts.limit ? parseInt(opts.limit) : undefined,
111
+ });
112
+
113
+ if (opts.json) {
114
+ console.log(JSON.stringify(pages, null, 2));
115
+ } else {
116
+ if (pages.length === 0) {
117
+ console.log("No pages found.");
118
+ return;
119
+ }
120
+ for (const p of pages) {
121
+ const status = p.status !== "published" ? ` [${p.status}]` : "";
122
+ const tags = p.tags.length ? ` (${p.tags.join(", ")})` : "";
123
+ console.log(` ${p.title}${status}${tags} — /${p.slug}`);
124
+ }
125
+ console.log(`\n${pages.length} page(s)`);
126
+ }
127
+ });
128
+
129
+ pageCmd
130
+ .command("update")
131
+ .description("Update a page")
132
+ .argument("<id>", "Page ID")
133
+ .option("--title <title>", "Title")
134
+ .option("--slug <slug>", "Slug")
135
+ .option("--content <content>", "Content")
136
+ .option("--format <format>", "Format")
137
+ .option("--category <category>", "Category")
138
+ .option("--parent <id>", "Parent page ID")
139
+ .option("--author <author>", "Author")
140
+ .option("--status <status>", "Status")
141
+ .option("--tags <tags>", "Comma-separated tags")
142
+ .option("--json", "Output as JSON", false)
143
+ .action((id, opts) => {
144
+ const input: Record<string, unknown> = {};
145
+ if (opts.title !== undefined) input.title = opts.title;
146
+ if (opts.slug !== undefined) input.slug = opts.slug;
147
+ if (opts.content !== undefined) input.content = opts.content;
148
+ if (opts.format !== undefined) input.format = opts.format;
149
+ if (opts.category !== undefined) input.category = opts.category;
150
+ if (opts.parent !== undefined) input.parent_id = opts.parent;
151
+ if (opts.author !== undefined) input.author = opts.author;
152
+ if (opts.status !== undefined) input.status = opts.status;
153
+ if (opts.tags !== undefined) input.tags = opts.tags.split(",").map((t: string) => t.trim());
154
+
155
+ const page = updatePage(id, input);
156
+ if (!page) {
157
+ console.error(`Page '${id}' not found.`);
158
+ process.exit(1);
159
+ }
160
+
161
+ if (opts.json) {
162
+ console.log(JSON.stringify(page, null, 2));
163
+ } else {
164
+ console.log(`Updated: ${page.title} (v${page.version})`);
165
+ }
166
+ });
167
+
168
+ pageCmd
169
+ .command("delete")
170
+ .description("Delete a page")
171
+ .argument("<id>", "Page ID")
172
+ .action((id) => {
173
+ const deleted = deletePage(id);
174
+ if (deleted) {
175
+ console.log(`Deleted page ${id}`);
176
+ } else {
177
+ console.error(`Page '${id}' not found.`);
178
+ process.exit(1);
179
+ }
180
+ });
181
+
182
+ pageCmd
183
+ .command("search")
184
+ .description("Search pages")
185
+ .argument("<query>", "Search term")
186
+ .option("--json", "Output as JSON", false)
187
+ .action((query, opts) => {
188
+ const results = searchPages(query);
189
+
190
+ if (opts.json) {
191
+ console.log(JSON.stringify(results, null, 2));
192
+ } else {
193
+ if (results.length === 0) {
194
+ console.log(`No pages matching "${query}".`);
195
+ return;
196
+ }
197
+ for (const p of results) {
198
+ console.log(` ${p.title} — /${p.slug}`);
199
+ }
200
+ }
201
+ });
202
+
203
+ pageCmd
204
+ .command("tree")
205
+ .description("Show page tree (hierarchical)")
206
+ .option("--json", "Output as JSON", false)
207
+ .action((opts) => {
208
+ const tree = getPageTree();
209
+
210
+ if (opts.json) {
211
+ console.log(JSON.stringify(tree, null, 2));
212
+ } else {
213
+ if (tree.length === 0) {
214
+ console.log("No pages found.");
215
+ return;
216
+ }
217
+ function printTree(nodes: PageTreeNode[], indent: number = 0) {
218
+ for (const node of nodes) {
219
+ const prefix = " ".repeat(indent);
220
+ console.log(`${prefix}${node.title} — /${node.slug}`);
221
+ if (node.children.length > 0) {
222
+ printTree(node.children, indent + 1);
223
+ }
224
+ }
225
+ }
226
+ printTree(tree);
227
+ }
228
+ });
229
+
230
+ pageCmd
231
+ .command("history")
232
+ .description("Show version history for a page")
233
+ .argument("<id>", "Page ID")
234
+ .option("--json", "Output as JSON", false)
235
+ .action((id, opts) => {
236
+ const history = getPageHistory(id);
237
+
238
+ if (opts.json) {
239
+ console.log(JSON.stringify(history, null, 2));
240
+ } else {
241
+ if (history.length === 0) {
242
+ console.log("No version history.");
243
+ return;
244
+ }
245
+ for (const v of history) {
246
+ console.log(` v${v.version} — ${v.title || "(no title)"} by ${v.author || "unknown"} at ${v.changed_at}`);
247
+ }
248
+ }
249
+ });
250
+
251
+ pageCmd
252
+ .command("revert")
253
+ .description("Revert a page to a previous version")
254
+ .argument("<id>", "Page ID")
255
+ .requiredOption("--version <n>", "Version number to revert to")
256
+ .option("--json", "Output as JSON", false)
257
+ .action((id, opts) => {
258
+ const page = revertToVersion(id, parseInt(opts.version));
259
+ if (!page) {
260
+ console.error(`Page '${id}' or version ${opts.version} not found.`);
261
+ process.exit(1);
262
+ }
263
+
264
+ if (opts.json) {
265
+ console.log(JSON.stringify(page, null, 2));
266
+ } else {
267
+ console.log(`Reverted to v${opts.version}: ${page.title} (now v${page.version})`);
268
+ }
269
+ });
270
+
271
+ pageCmd
272
+ .command("recent")
273
+ .description("Show recently updated pages")
274
+ .option("--limit <n>", "Number of results", "10")
275
+ .option("--json", "Output as JSON", false)
276
+ .action((opts) => {
277
+ const pages = getRecentlyUpdated(parseInt(opts.limit));
278
+
279
+ if (opts.json) {
280
+ console.log(JSON.stringify(pages, null, 2));
281
+ } else {
282
+ if (pages.length === 0) {
283
+ console.log("No pages found.");
284
+ return;
285
+ }
286
+ for (const p of pages) {
287
+ console.log(` ${p.title} — updated ${p.updated_at}`);
288
+ }
289
+ }
290
+ });
291
+
292
+ // --- Categories & Tags (top-level for convenience) ---
293
+
294
+ program
295
+ .command("categories")
296
+ .description("List all categories")
297
+ .option("--json", "Output as JSON", false)
298
+ .action((opts) => {
299
+ const pages = listPages();
300
+ const categories = [...new Set(pages.map((p) => p.category).filter(Boolean))] as string[];
301
+ categories.sort();
302
+
303
+ if (opts.json) {
304
+ console.log(JSON.stringify(categories));
305
+ } else {
306
+ if (categories.length === 0) {
307
+ console.log("No categories found.");
308
+ return;
309
+ }
310
+ for (const cat of categories) {
311
+ const count = pages.filter((p) => p.category === cat).length;
312
+ console.log(` ${cat} (${count})`);
313
+ }
314
+ }
315
+ });
316
+
317
+ program
318
+ .command("tags")
319
+ .description("List all tags")
320
+ .option("--json", "Output as JSON", false)
321
+ .action((opts) => {
322
+ const pages = listPages();
323
+ const tagCounts = new Map<string, number>();
324
+ for (const p of pages) {
325
+ for (const tag of p.tags) {
326
+ tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
327
+ }
328
+ }
329
+
330
+ const tags = [...tagCounts.entries()].sort((a, b) => a[0].localeCompare(b[0]));
331
+
332
+ if (opts.json) {
333
+ console.log(JSON.stringify(Object.fromEntries(tags)));
334
+ } else {
335
+ if (tags.length === 0) {
336
+ console.log("No tags found.");
337
+ return;
338
+ }
339
+ for (const [tag, count] of tags) {
340
+ console.log(` ${tag} (${count})`);
341
+ }
342
+ }
343
+ });
344
+
345
+ program.parse(process.argv);
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Database connection for microservice-wiki
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-wiki", "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-wiki", "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-wiki", "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,55 @@
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 pages (
13
+ id TEXT PRIMARY KEY,
14
+ title TEXT NOT NULL,
15
+ slug TEXT UNIQUE,
16
+ content TEXT,
17
+ format TEXT NOT NULL DEFAULT 'markdown' CHECK(format IN ('markdown', 'html')),
18
+ category TEXT,
19
+ parent_id TEXT,
20
+ author TEXT,
21
+ status TEXT NOT NULL DEFAULT 'published' CHECK(status IN ('draft', 'published', 'archived')),
22
+ tags TEXT NOT NULL DEFAULT '[]',
23
+ version INTEGER NOT NULL DEFAULT 1,
24
+ metadata TEXT NOT NULL DEFAULT '{}',
25
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
26
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
27
+ );
28
+
29
+ CREATE TABLE IF NOT EXISTS page_versions (
30
+ id TEXT PRIMARY KEY,
31
+ page_id TEXT NOT NULL REFERENCES pages(id) ON DELETE CASCADE,
32
+ version INTEGER NOT NULL,
33
+ title TEXT,
34
+ content TEXT,
35
+ author TEXT,
36
+ changed_at TEXT NOT NULL DEFAULT (datetime('now'))
37
+ );
38
+
39
+ CREATE TABLE IF NOT EXISTS page_links (
40
+ source_id TEXT NOT NULL REFERENCES pages(id) ON DELETE CASCADE,
41
+ target_id TEXT NOT NULL REFERENCES pages(id) ON DELETE CASCADE,
42
+ PRIMARY KEY (source_id, target_id)
43
+ );
44
+
45
+ CREATE INDEX IF NOT EXISTS idx_pages_slug ON pages(slug);
46
+ CREATE INDEX IF NOT EXISTS idx_pages_category ON pages(category);
47
+ CREATE INDEX IF NOT EXISTS idx_pages_parent ON pages(parent_id);
48
+ CREATE INDEX IF NOT EXISTS idx_pages_status ON pages(status);
49
+ CREATE INDEX IF NOT EXISTS idx_pages_updated ON pages(updated_at);
50
+ CREATE INDEX IF NOT EXISTS idx_page_versions_page ON page_versions(page_id);
51
+ CREATE INDEX IF NOT EXISTS idx_page_links_source ON page_links(source_id);
52
+ CREATE INDEX IF NOT EXISTS idx_page_links_target ON page_links(target_id);
53
+ `,
54
+ },
55
+ ];