@tekmidian/pai 0.66.1 → 0.66.3

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 (185) hide show
  1. package/dist/{aibroker-client-B8c42Lh8.mjs → aibroker-client-Ck3p3k_-.mjs} +2 -2
  2. package/dist/{aibroker-client-C5Fw7DNz.mjs → aibroker-client-WCZi_tn-.mjs} +3 -3
  3. package/dist/{aibroker-client-C5Fw7DNz.mjs.map → aibroker-client-WCZi_tn-.mjs.map} +1 -1
  4. package/dist/{auto-route-o0BOsXn7.mjs → auto-route-BnizyALK.mjs} +65 -4
  5. package/dist/auto-route-BnizyALK.mjs.map +1 -0
  6. package/dist/auto-route-D6fW1Q8z.mjs +3 -0
  7. package/dist/{chain-CZfKIEq9.mjs → chain-Fz4NLm4D.mjs} +3 -4
  8. package/dist/{chain-CZfKIEq9.mjs.map → chain-Fz4NLm4D.mjs.map} +1 -1
  9. package/dist/cli/index.mjs +18 -23
  10. package/dist/cli/index.mjs.map +1 -1
  11. package/dist/cli/program.mjs +17 -22
  12. package/dist/{config-YinjgXEJ.mjs → config-CnsDMETf.mjs} +1 -1
  13. package/dist/{config-BbLFD7Uf.mjs → config-Ddc4DIPk.mjs} +5 -3
  14. package/dist/config-Ddc4DIPk.mjs.map +1 -0
  15. package/dist/{context-handover-cache-pkHzmL3c.mjs → context-handover-cache-DcPEW6vf.mjs} +1221 -13
  16. package/dist/context-handover-cache-DcPEW6vf.mjs.map +1 -0
  17. package/dist/daemon/index.mjs +17 -19
  18. package/dist/daemon/index.mjs.map +1 -1
  19. package/dist/daemon-Bl8TpHdM.mjs +5048 -0
  20. package/dist/daemon-Bl8TpHdM.mjs.map +1 -0
  21. package/dist/daemon-ZhHU6D0i.mjs +18 -0
  22. package/dist/daemon-mcp/index.mjs +12 -9
  23. package/dist/daemon-mcp/index.mjs.map +1 -1
  24. package/dist/{embeddings-CcscYWwk.mjs → embeddings-D0WOQpYR.mjs} +1 -1
  25. package/dist/{embeddings-Bx3q0QOY.mjs → embeddings-DMVzpvRZ.mjs} +18 -9
  26. package/dist/embeddings-DMVzpvRZ.mjs.map +1 -0
  27. package/dist/{env-JNEIrQWg.mjs → env-DiolswKQ.mjs} +1 -1
  28. package/dist/{env-JNEIrQWg.mjs.map → env-DiolswKQ.mjs.map} +1 -1
  29. package/dist/factory-BUnZgnKW.mjs +2892 -0
  30. package/dist/factory-BUnZgnKW.mjs.map +1 -0
  31. package/dist/factory-CrVlo2hY.mjs +8 -0
  32. package/dist/{fallback-CIiq2Raw.mjs → fallback-awOmq_9A.mjs} +23 -11
  33. package/dist/fallback-awOmq_9A.mjs.map +1 -0
  34. package/dist/{chunker-BH4i-F2b.mjs → helpers-CZsi_49C.mjs} +221 -2
  35. package/dist/helpers-CZsi_49C.mjs.map +1 -0
  36. package/dist/hooks/block-sleep-poll.mjs +6 -5
  37. package/dist/hooks/block-sleep-poll.mjs.map +3 -3
  38. package/dist/hooks/context-compression-hook.mjs +1 -0
  39. package/dist/hooks/context-compression-hook.mjs.map +2 -2
  40. package/dist/hooks/load-project-context.mjs +6 -5
  41. package/dist/hooks/load-project-context.mjs.map +3 -3
  42. package/dist/hooks/post-compact-inject.mjs +6 -5
  43. package/dist/hooks/post-compact-inject.mjs.map +3 -3
  44. package/dist/hooks/route-agents-to-worker.mjs +6 -5
  45. package/dist/hooks/route-agents-to-worker.mjs.map +3 -3
  46. package/dist/hooks/security-validator.mjs +1 -0
  47. package/dist/hooks/security-validator.mjs.map +2 -2
  48. package/dist/hooks/whisper-rules.mjs +1 -0
  49. package/dist/hooks/whisper-rules.mjs.map +2 -2
  50. package/dist/hooks/worker-guard.mjs +6 -5
  51. package/dist/hooks/worker-guard.mjs.map +3 -3
  52. package/dist/hooks/worker-proxy.mjs +6 -5
  53. package/dist/hooks/worker-proxy.mjs.map +3 -3
  54. package/dist/hooks/worker-status-line.mjs +6 -5
  55. package/dist/hooks/worker-status-line.mjs.map +3 -3
  56. package/dist/hooks/worker-supervision.mjs +6 -5
  57. package/dist/hooks/worker-supervision.mjs.map +3 -3
  58. package/dist/index.mjs +6 -7
  59. package/dist/{ipc-client-D16Xw6Uo.mjs → ipc-client-DkxRay-o.mjs} +2 -2
  60. package/dist/{ipc-client-D16Xw6Uo.mjs.map → ipc-client-DkxRay-o.mjs.map} +1 -1
  61. package/dist/{main-resolver-DPPwHNtn.mjs → main-resolver-CVGPICy-.mjs} +7 -7
  62. package/dist/{main-resolver-DPPwHNtn.mjs.map → main-resolver-CVGPICy-.mjs.map} +1 -1
  63. package/dist/main-resolver-DDShdDBm.mjs +7 -0
  64. package/dist/{migrate-BD7D8EEh.mjs → migrate-Bq0esMOa.mjs} +2 -2
  65. package/dist/{migrate-BD7D8EEh.mjs.map → migrate-Bq0esMOa.mjs.map} +1 -1
  66. package/dist/{pai-home-Cm9rcJgX.mjs → pai-home-UncxWxlX.mjs} +1 -1
  67. package/dist/{pai-home-Cm9rcJgX.mjs.map → pai-home-UncxWxlX.mjs.map} +1 -1
  68. package/dist/{pai-marker-D1MMswkz.mjs → pai-marker-DXVpFsYz.mjs} +1 -1
  69. package/dist/{pai-marker-D1MMswkz.mjs.map → pai-marker-DXVpFsYz.mjs.map} +1 -1
  70. package/dist/{planner-Dm5Acyo1.mjs → planner-GdiYe2FO.mjs} +10 -7
  71. package/dist/{planner-Dm5Acyo1.mjs.map → planner-GdiYe2FO.mjs.map} +1 -1
  72. package/dist/postgres-DKyiCA5V.mjs +5 -0
  73. package/dist/{postgres-Ceqsa64C.mjs → postgres-H85VSYHI.mjs} +364 -9
  74. package/dist/postgres-H85VSYHI.mjs.map +1 -0
  75. package/dist/{program-JJ0TwSHI.mjs → program-D3MSfx86.mjs} +142 -100
  76. package/dist/program-D3MSfx86.mjs.map +1 -0
  77. package/dist/{query-feedback-BIaZTTFO.mjs → query-feedback-BPdapXKd.mjs} +2 -2
  78. package/dist/{query-feedback-BIaZTTFO.mjs.map → query-feedback-BPdapXKd.mjs.map} +1 -1
  79. package/dist/{reranker-DKv80KO5.mjs → reranker-3lnggwgq.mjs} +1 -1
  80. package/dist/{reranker-DKv80KO5.mjs.map → reranker-3lnggwgq.mjs.map} +1 -1
  81. package/dist/{reranker-CFiEzHQu.mjs → reranker-CZ2mP4cf.mjs} +1 -1
  82. package/dist/router-BK-hFeQ6.mjs +3 -0
  83. package/dist/{router-a5G7q8_3.mjs → router-BaTbc9VX.mjs} +2 -2
  84. package/dist/{router-a5G7q8_3.mjs.map → router-BaTbc9VX.mjs.map} +1 -1
  85. package/dist/{run-BCpbxqA5.mjs → run-D9YbWsnL.mjs} +599 -22
  86. package/dist/run-D9YbWsnL.mjs.map +1 -0
  87. package/dist/{run-env-DRcp7A8K.mjs → run-env-CQRnxNht.mjs} +3 -3
  88. package/dist/{run-env-DRcp7A8K.mjs.map → run-env-CQRnxNht.mjs.map} +1 -1
  89. package/dist/{runtime-paths-CHTg3ywb.mjs → runtime-paths-Bw3Jk8qJ.mjs} +4 -2
  90. package/dist/runtime-paths-Bw3Jk8qJ.mjs.map +1 -0
  91. package/dist/{search-BOSphCJ1.mjs → search-CNAGTiJP.mjs} +2 -2
  92. package/dist/{search-BOSphCJ1.mjs.map → search-CNAGTiJP.mjs.map} +1 -1
  93. package/dist/{sources-CijVso3n.mjs → sources-CM2g-CLT.mjs} +2 -2
  94. package/dist/{sources-CijVso3n.mjs.map → sources-CM2g-CLT.mjs.map} +1 -1
  95. package/dist/{stop-words-BdQuaE9K.mjs → stop-words-DtxaTWU_.mjs} +1 -1
  96. package/dist/{stop-words-BdQuaE9K.mjs.map → stop-words-DtxaTWU_.mjs.map} +1 -1
  97. package/dist/{utils-C9HsYDpO.mjs → utils-DddRwMsG.mjs} +2 -2
  98. package/dist/{utils-C9HsYDpO.mjs.map → utils-DddRwMsG.mjs.map} +1 -1
  99. package/dist/{zettelkasten-CtGHQWPU.mjs → zettelkasten-BRIaHzFm.mjs} +149 -7
  100. package/dist/zettelkasten-BRIaHzFm.mjs.map +1 -0
  101. package/dist/zettelkasten-G0-ZTybr.mjs +5 -0
  102. package/docs/commands/memory.md +2 -0
  103. package/docs/commands/worker.md +2 -0
  104. package/package.json +1 -1
  105. package/dist/async-C2Bm_Lal.mjs +0 -300
  106. package/dist/async-C2Bm_Lal.mjs.map +0 -1
  107. package/dist/auto-route-o0BOsXn7.mjs.map +0 -1
  108. package/dist/chunker-BH4i-F2b.mjs.map +0 -1
  109. package/dist/clusters-BCtD3fbe.mjs +0 -169
  110. package/dist/clusters-BCtD3fbe.mjs.map +0 -1
  111. package/dist/config-BbLFD7Uf.mjs.map +0 -1
  112. package/dist/context-handover-cache-pkHzmL3c.mjs.map +0 -1
  113. package/dist/daemon-DXlaVCkw.mjs +0 -1741
  114. package/dist/daemon-DXlaVCkw.mjs.map +0 -1
  115. package/dist/daemon-DyORmxZu.mjs +0 -19
  116. package/dist/detector-BEPFsINR.mjs +0 -3
  117. package/dist/detector-BRTtAWrM.mjs +0 -65
  118. package/dist/detector-BRTtAWrM.mjs.map +0 -1
  119. package/dist/embeddings-Bx3q0QOY.mjs.map +0 -1
  120. package/dist/factory-CNpPQQ-5.mjs +0 -249
  121. package/dist/factory-CNpPQQ-5.mjs.map +0 -1
  122. package/dist/factory-DY7x73mE.mjs +0 -3
  123. package/dist/fallback-CIiq2Raw.mjs.map +0 -1
  124. package/dist/federation-db-BTyoufBh.mjs +0 -139
  125. package/dist/federation-db-BTyoufBh.mjs.map +0 -1
  126. package/dist/federation-db-HfIFc7FG.mjs +0 -3
  127. package/dist/helpers-BRCJg0G3.mjs +0 -223
  128. package/dist/helpers-BRCJg0G3.mjs.map +0 -1
  129. package/dist/indexer-backend-DFF2FrYx.mjs +0 -5
  130. package/dist/indexer-backend-isSLg6yE.mjs +0 -1
  131. package/dist/kg-entity-DCOcsVFD.mjs +0 -29
  132. package/dist/kg-entity-DCOcsVFD.mjs.map +0 -1
  133. package/dist/latent-ideas-wwAXquGe.mjs +0 -191
  134. package/dist/latent-ideas-wwAXquGe.mjs.map +0 -1
  135. package/dist/link-boost-CnI7UVMJ.mjs +0 -34
  136. package/dist/link-boost-CnI7UVMJ.mjs.map +0 -1
  137. package/dist/main-resolver-BKz_OuEh.mjs +0 -7
  138. package/dist/merge-2gqRPFu2.mjs +0 -3
  139. package/dist/merge-DgU9OgZy.mjs +0 -6
  140. package/dist/merge-DgU9OgZy.mjs.map +0 -1
  141. package/dist/module-paths-DdRzbkUI.mjs +0 -44
  142. package/dist/module-paths-DdRzbkUI.mjs.map +0 -1
  143. package/dist/neighborhood-2FsmzoxG.mjs +0 -114
  144. package/dist/neighborhood-2FsmzoxG.mjs.map +0 -1
  145. package/dist/note-context-De63k8na.mjs +0 -106
  146. package/dist/note-context-De63k8na.mjs.map +0 -1
  147. package/dist/postgres-Ceqsa64C.mjs.map +0 -1
  148. package/dist/program-JJ0TwSHI.mjs.map +0 -1
  149. package/dist/query-feedback-B_iigYj-.mjs +0 -3
  150. package/dist/registry-db-C7voqML9.mjs +0 -213
  151. package/dist/registry-db-C7voqML9.mjs.map +0 -1
  152. package/dist/registry-db-JHPhA8vF.mjs +0 -3
  153. package/dist/registry-postgres-nOfyBSIW.mjs +0 -795
  154. package/dist/registry-postgres-nOfyBSIW.mjs.map +0 -1
  155. package/dist/registry-sqlite-DrCW1aRK.mjs +0 -593
  156. package/dist/registry-sqlite-DrCW1aRK.mjs.map +0 -1
  157. package/dist/router-CXUGsv85.mjs +0 -3
  158. package/dist/run-BCpbxqA5.mjs.map +0 -1
  159. package/dist/runtime-paths-CHTg3ywb.mjs.map +0 -1
  160. package/dist/search-Bf3Kub3F.mjs +0 -3
  161. package/dist/server-DgmAHyFK.mjs +0 -403
  162. package/dist/server-DgmAHyFK.mjs.map +0 -1
  163. package/dist/session-keepalive-DxjTHGcK.mjs +0 -582
  164. package/dist/session-keepalive-DxjTHGcK.mjs.map +0 -1
  165. package/dist/sqlite-CqsTy6xo.mjs +0 -936
  166. package/dist/sqlite-CqsTy6xo.mjs.map +0 -1
  167. package/dist/state-DW8zdweW.mjs +0 -3
  168. package/dist/state-HyjqTihC.mjs +0 -76
  169. package/dist/state-HyjqTihC.mjs.map +0 -1
  170. package/dist/themes-CTaOj3e1.mjs +0 -148
  171. package/dist/themes-CTaOj3e1.mjs.map +0 -1
  172. package/dist/tools-BcOKPBtg.mjs +0 -1489
  173. package/dist/tools-BcOKPBtg.mjs.map +0 -1
  174. package/dist/tools-C-_l0o8Z.mjs +0 -6
  175. package/dist/trace-CtV0RO6n.mjs +0 -137
  176. package/dist/trace-CtV0RO6n.mjs.map +0 -1
  177. package/dist/utils-BrX9FLeK.mjs +0 -3
  178. package/dist/vault-indexer-BG1sEVyM.mjs +0 -537
  179. package/dist/vault-indexer-BG1sEVyM.mjs.map +0 -1
  180. package/dist/wakeup-D8n33hYV.mjs +0 -335
  181. package/dist/wakeup-D8n33hYV.mjs.map +0 -1
  182. package/dist/work-queue-worker-4tLa5gpQ.mjs +0 -567
  183. package/dist/work-queue-worker-4tLa5gpQ.mjs.map +0 -1
  184. package/dist/work-queue-worker-rFZvTO3p.mjs +0 -11
  185. package/dist/zettelkasten-CtGHQWPU.mjs.map +0 -1
@@ -1,795 +0,0 @@
1
- import pg from "pg";
2
-
3
- //#region src/storage/registry-postgres.ts
4
- /**
5
- * PostgresRegistryBackend — implements RegistryBackend over the pg pool
6
- * pattern already used by PostgresBackend (src/storage/postgres/backend.ts).
7
- *
8
- * Same SQL shape as SQLiteRegistryBackend, translated to $-placeholders.
9
- * Behavioural notes where Postgres diverges from SQLite's "OR IGNORE":
10
- * - `INSERT ... ON CONFLICT DO NOTHING` (no target) catches any unique
11
- * violation on the table, matching SQLite's INSERT OR IGNORE.
12
- * - SQLite's `UPDATE OR IGNORE` (used once, in reassignLinksTarget/
13
- * applyProjectMerge's link retarget) has no direct Postgres equivalent —
14
- * an UPDATE cannot specify ON CONFLICT. Replicated with a NOT EXISTS guard
15
- * that skips rows whose new (session_id, target_project_id) would collide,
16
- * leaving them for the caller's existing cleanup DELETE, exactly as the
17
- * SQLite version does.
18
- */
19
- const { Pool: PgPool } = pg;
20
- /**
21
- * node-postgres returns BIGINT columns (created_at/updated_at/archived_at/
22
- * closed_at, and MAX() over a BIGINT column) as strings — JS numbers can't
23
- * safely hold the full 64-bit range. Every value here is Date.now() ms and
24
- * well inside Number.isSafeInteger, so a plain Number() conversion keeps the
25
- * shape identical to better-sqlite3's row objects (same pattern as
26
- * rowToKgEntity in postgres/backend.ts).
27
- */
28
- function toNum(v) {
29
- return typeof v === "string" ? Number(v) : v;
30
- }
31
- function toNumOrNull(v) {
32
- return v === null || v === void 0 ? null : toNum(v);
33
- }
34
- function mapProject(row) {
35
- return {
36
- ...row,
37
- created_at: toNum(row.created_at),
38
- updated_at: toNum(row.updated_at),
39
- archived_at: toNumOrNull(row.archived_at)
40
- };
41
- }
42
- function mapProjectWithStats(row) {
43
- return {
44
- ...mapProject(row),
45
- session_count: toNum(row.session_count),
46
- last_active: toNumOrNull(row.last_active)
47
- };
48
- }
49
- function mapSession(row) {
50
- return {
51
- ...row,
52
- created_at: toNum(row.created_at),
53
- closed_at: toNumOrNull(row.closed_at)
54
- };
55
- }
56
- var PostgresRegistryBackend = class {
57
- backendType = "postgres";
58
- pool;
59
- /** False when constructed from a pool owned elsewhere (the shared storage
60
- * backend pool, wired by src/storage/factory.ts) — close() must not end
61
- * a pool this instance doesn't own. */
62
- ownsPool;
63
- constructor(configOrPool) {
64
- if (configOrPool instanceof PgPool) {
65
- this.pool = configOrPool;
66
- this.ownsPool = false;
67
- return;
68
- }
69
- const config = configOrPool;
70
- this.pool = new PgPool({
71
- connectionString: config.connectionString ?? `postgresql://${config.user ?? "pai"}:${config.password ?? "pai"}@${config.host ?? "localhost"}:${config.port ?? 5432}/${config.database ?? "pai"}`,
72
- max: config.maxConnections ?? 5,
73
- connectionTimeoutMillis: config.connectionTimeoutMs ?? 5e3,
74
- idleTimeoutMillis: 3e4,
75
- allowExitOnIdle: true
76
- });
77
- this.ownsPool = true;
78
- this.pool.on("error", (err) => {
79
- process.stderr.write(`[pai-postgres] Registry pool error: ${err.message}\n`);
80
- });
81
- }
82
- async close() {
83
- if (this.ownsPool) await this.pool.end();
84
- }
85
- async resetRegistry() {
86
- const client = await this.pool.connect();
87
- try {
88
- await client.query("BEGIN");
89
- await client.query("DELETE FROM links");
90
- await client.query("DELETE FROM compaction_log");
91
- await client.query("DELETE FROM session_tags");
92
- await client.query("DELETE FROM project_tags");
93
- await client.query("DELETE FROM aliases");
94
- await client.query("DELETE FROM sessions");
95
- await client.query("DELETE FROM tags");
96
- await client.query("DELETE FROM projects");
97
- await client.query("COMMIT");
98
- } catch (e) {
99
- await client.query("ROLLBACK");
100
- throw e;
101
- } finally {
102
- client.release();
103
- }
104
- }
105
- async getProjectById(id) {
106
- const r = await this.pool.query("SELECT * FROM projects WHERE id = $1", [id]);
107
- return r.rows[0] ? mapProject(r.rows[0]) : null;
108
- }
109
- async getProjectBySlug(slug, opts = {}) {
110
- let sql = opts.caseInsensitive ? "SELECT * FROM projects WHERE lower(slug) = lower($1)" : "SELECT * FROM projects WHERE slug = $1";
111
- const params = [slug];
112
- if (opts.excludeId !== void 0) {
113
- params.push(opts.excludeId);
114
- sql += ` AND id != $${params.length}`;
115
- }
116
- const r = await this.pool.query(sql, params);
117
- return r.rows[0] ? mapProject(r.rows[0]) : null;
118
- }
119
- async getProjectByAlias(alias, opts = {}) {
120
- const sql = opts.caseInsensitive ? "SELECT p.* FROM projects p JOIN aliases a ON a.project_id = p.id WHERE lower(a.alias) = lower($1)" : "SELECT p.* FROM projects p JOIN aliases a ON a.project_id = p.id WHERE a.alias = $1";
121
- const r = await this.pool.query(sql, [alias]);
122
- return r.rows[0] ? mapProject(r.rows[0]) : null;
123
- }
124
- async getProjectByRootPath(rootPath, opts = {}) {
125
- let sql = "SELECT * FROM projects WHERE root_path = $1";
126
- const params = [rootPath];
127
- if (opts.excludeId !== void 0) {
128
- params.push(opts.excludeId);
129
- sql += ` AND id != $${params.length}`;
130
- }
131
- const r = await this.pool.query(sql, params);
132
- return r.rows[0] ? mapProject(r.rows[0]) : null;
133
- }
134
- async getProjectByEncodedDir(encodedDir, opts = {}) {
135
- let sql = "SELECT * FROM projects WHERE encoded_dir = $1";
136
- const params = [encodedDir];
137
- if (opts.excludeId !== void 0) {
138
- params.push(opts.excludeId);
139
- sql += ` AND id != $${params.length}`;
140
- }
141
- const r = await this.pool.query(sql, params);
142
- return r.rows[0] ? mapProject(r.rows[0]) : null;
143
- }
144
- async findProjectByCwdPrefix(cwd) {
145
- const r = await this.pool.query(`SELECT * FROM projects WHERE status = 'active' AND $1 LIKE root_path || '%'
146
- ORDER BY length(root_path) DESC LIMIT 1`, [cwd]);
147
- return r.rows[0] ? mapProject(r.rows[0]) : null;
148
- }
149
- async listProjects(opts = {}) {
150
- const where = [];
151
- const params = [];
152
- if (opts.status) {
153
- params.push(opts.status);
154
- where.push(`p.status = $${params.length}`);
155
- }
156
- if (opts.excludeArchived) where.push("p.status != 'archived'");
157
- if (opts.tagName) {
158
- params.push(opts.tagName);
159
- where.push(`p.id IN (SELECT pt.project_id FROM project_tags pt JOIN tags t ON pt.tag_id = t.id WHERE t.name = $${params.length})`);
160
- }
161
- let sql = "SELECT p.* FROM projects p";
162
- if (where.length) sql += " WHERE " + where.join(" AND ");
163
- switch (opts.orderBy) {
164
- case "updated_desc":
165
- sql += " ORDER BY p.updated_at DESC";
166
- break;
167
- case "slug":
168
- sql += " ORDER BY p.slug ASC";
169
- break;
170
- case "id":
171
- sql += " ORDER BY p.id";
172
- break;
173
- default:
174
- sql += " ORDER BY p.status ASC, p.updated_at DESC";
175
- break;
176
- }
177
- if (opts.limit !== void 0) {
178
- params.push(opts.limit);
179
- sql += ` LIMIT $${params.length}`;
180
- }
181
- return (await this.pool.query(sql, params)).rows.map(mapProject);
182
- }
183
- async listProjectsByPathLengthDesc(opts = {}) {
184
- const where = opts.excludeArchived ? "WHERE status != 'archived'" : "";
185
- return (await this.pool.query(`SELECT * FROM projects ${where} ORDER BY LENGTH(root_path) DESC`)).rows.map(mapProject);
186
- }
187
- async listProjectsWithSessionStats(opts = {}) {
188
- const where = [];
189
- const params = [];
190
- if (opts.status) {
191
- params.push(opts.status);
192
- where.push(`p.status = $${params.length}`);
193
- }
194
- if (opts.tagId !== void 0) {
195
- params.push(opts.tagId);
196
- where.push(`p.id IN (SELECT project_id FROM project_tags WHERE tag_id = $${params.length})`);
197
- }
198
- let sql = `
199
- SELECT p.*,
200
- (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id)::int AS session_count,
201
- (SELECT MAX(s.created_at) FROM sessions s WHERE s.project_id = p.id) AS last_active
202
- FROM projects p
203
- `;
204
- if (where.length) sql += " WHERE " + where.join(" AND ");
205
- sql += opts.orderBy === "updated_desc" ? " ORDER BY p.updated_at DESC" : " ORDER BY p.status ASC, p.updated_at DESC";
206
- return (await this.pool.query(sql, params)).rows.map(mapProjectWithStats);
207
- }
208
- async listNamedProjects(opts = {}) {
209
- const sql = `
210
- SELECT p.*, a.alias AS name,
211
- (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id)::int AS session_count,
212
- (SELECT MAX(s.created_at) FROM sessions s WHERE s.project_id = p.id) AS last_active
213
- FROM projects p
214
- ${opts.includeUnnamed ? "LEFT JOIN" : "JOIN"} aliases a ON a.project_id = p.id
215
- WHERE p.status = 'active'
216
- ORDER BY p.updated_at DESC
217
- `;
218
- return (await this.pool.query(sql)).rows.map((row) => ({
219
- ...mapProjectWithStats(row),
220
- name: row.name
221
- }));
222
- }
223
- async searchProjects(query, limit = 20) {
224
- const q = `%${query}%`;
225
- return (await this.pool.query(`SELECT * FROM projects WHERE slug LIKE $1 OR display_name LIKE $1 OR root_path LIKE $1
226
- ORDER BY updated_at DESC LIMIT $2`, [q, limit])).rows.map(mapProject);
227
- }
228
- async countProjects(opts = {}) {
229
- const r = opts.status ? await this.pool.query("SELECT COUNT(*)::text AS n FROM projects WHERE status = $1", [opts.status]) : await this.pool.query("SELECT COUNT(*)::text AS n FROM projects");
230
- return parseInt(r.rows[0].n, 10);
231
- }
232
- async getMostRecentProjectUpdatedAt() {
233
- const r = await this.pool.query("SELECT updated_at FROM projects ORDER BY updated_at DESC LIMIT 1");
234
- return r.rows[0] ? Number(r.rows[0].updated_at) : null;
235
- }
236
- async countChildProjects(parentId) {
237
- const r = await this.pool.query("SELECT COUNT(*)::text AS n FROM projects WHERE parent_id = $1", [parentId]);
238
- return parseInt(r.rows[0].n, 10);
239
- }
240
- async findSiblingProjectsBySlugPattern(excludeProjectId, slug, limit = 3) {
241
- return (await this.pool.query(`SELECT p.slug, p.root_path, COUNT(s.id)::int AS session_count
242
- FROM projects p LEFT JOIN sessions s ON s.project_id = p.id
243
- WHERE p.id != $1 AND (p.slug = $2 OR p.slug LIKE $2 || '-%' OR $2 LIKE p.slug || '-%')
244
- GROUP BY p.id HAVING COUNT(s.id) > 0 ORDER BY session_count DESC LIMIT $3`, [
245
- excludeProjectId,
246
- slug,
247
- limit
248
- ])).rows;
249
- }
250
- async createProject(input) {
251
- const type = input.type ?? "local";
252
- const status = input.status ?? "active";
253
- return mapProject((await this.pool.query(`INSERT INTO projects (slug, display_name, root_path, encoded_dir, type, status, created_at, updated_at)
254
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *`, [
255
- input.slug,
256
- input.displayName,
257
- input.rootPath,
258
- input.encodedDir,
259
- type,
260
- status,
261
- input.createdAt,
262
- input.updatedAt
263
- ])).rows[0]);
264
- }
265
- async createProjectWithSlugRetry(input) {
266
- let slug = input.baseSlug;
267
- let attempt = 0;
268
- while (true) {
269
- const r = await this.pool.query(`INSERT INTO projects (slug, display_name, root_path, encoded_dir, type, status, created_at, updated_at)
270
- VALUES ($1, $2, $3, $4, 'local', 'active', $5, $6)
271
- ON CONFLICT DO NOTHING RETURNING id`, [
272
- slug,
273
- input.displayName,
274
- input.rootPath,
275
- input.encodedDir,
276
- input.createdAt,
277
- input.updatedAt
278
- ]);
279
- if (r.rows.length > 0) return {
280
- id: r.rows[0].id,
281
- slug,
282
- created: true
283
- };
284
- const existing = await this.pool.query("SELECT id FROM projects WHERE root_path = $1", [input.rootPath]);
285
- if (existing.rows[0]) return {
286
- id: existing.rows[0].id,
287
- slug,
288
- created: false
289
- };
290
- attempt++;
291
- slug = `${input.baseSlug}-${attempt}`;
292
- }
293
- }
294
- async updateProjectPath(id, patch, updatedAt) {
295
- const sets = [];
296
- const params = [];
297
- if (patch.rootPath !== void 0) {
298
- params.push(patch.rootPath);
299
- sets.push(`root_path = $${params.length}`);
300
- }
301
- if (patch.encodedDir !== void 0) {
302
- params.push(patch.encodedDir);
303
- sets.push(`encoded_dir = $${params.length}`);
304
- }
305
- if (updatedAt !== void 0) {
306
- params.push(updatedAt);
307
- sets.push(`updated_at = $${params.length}`);
308
- }
309
- if (!sets.length) return;
310
- params.push(id);
311
- await this.pool.query(`UPDATE projects SET ${sets.join(", ")} WHERE id = $${params.length}`, params);
312
- }
313
- async updateProjectStatus(id, status, opts = {}) {
314
- const sets = [];
315
- const params = [status];
316
- sets.push("status = $1");
317
- if (opts.archivedAt !== void 0) {
318
- params.push(opts.archivedAt);
319
- sets.push(`archived_at = $${params.length}`);
320
- }
321
- if (opts.updatedAt !== void 0) {
322
- params.push(opts.updatedAt);
323
- sets.push(`updated_at = $${params.length}`);
324
- }
325
- params.push(id);
326
- let sql = `UPDATE projects SET ${sets.join(", ")} WHERE id = $${params.length}`;
327
- if (opts.requireCurrentStatus) {
328
- params.push(opts.requireCurrentStatus);
329
- sql += ` AND status = $${params.length}`;
330
- }
331
- await this.pool.query(sql, params);
332
- }
333
- async updateProjectDisplayName(id, displayName, updatedAt) {
334
- if (updatedAt !== void 0) await this.pool.query("UPDATE projects SET display_name = $1, updated_at = $2 WHERE id = $3", [
335
- displayName,
336
- updatedAt,
337
- id
338
- ]);
339
- else await this.pool.query("UPDATE projects SET display_name = $1 WHERE id = $2", [displayName, id]);
340
- }
341
- async updateProjectType(id, type, updatedAt) {
342
- if (updatedAt !== void 0) await this.pool.query("UPDATE projects SET type = $1, updated_at = $2 WHERE id = $3", [
343
- type,
344
- updatedAt,
345
- id
346
- ]);
347
- else await this.pool.query("UPDATE projects SET type = $1 WHERE id = $2", [type, id]);
348
- }
349
- async updateProjectSessionConfig(id, config, updatedAt) {
350
- if (updatedAt !== void 0) await this.pool.query("UPDATE projects SET session_config = $1, updated_at = $2 WHERE id = $3", [
351
- config,
352
- updatedAt,
353
- id
354
- ]);
355
- else await this.pool.query("UPDATE projects SET session_config = $1 WHERE id = $2", [config, id]);
356
- }
357
- async updateProjectClaudeNotesDir(id, dir, updatedAt) {
358
- if (updatedAt !== void 0) await this.pool.query("UPDATE projects SET claude_notes_dir = $1, updated_at = $2 WHERE id = $3", [
359
- dir,
360
- updatedAt,
361
- id
362
- ]);
363
- else await this.pool.query("UPDATE projects SET claude_notes_dir = $1 WHERE id = $2", [dir, id]);
364
- }
365
- async updateProjectObsidianLink(id, link, updatedAt) {
366
- if (updatedAt !== void 0) await this.pool.query("UPDATE projects SET obsidian_link = $1, updated_at = $2 WHERE id = $3", [
367
- link,
368
- updatedAt,
369
- id
370
- ]);
371
- else await this.pool.query("UPDATE projects SET obsidian_link = $1 WHERE id = $2", [link, id]);
372
- }
373
- async updateProjectSlug(id, slug, updatedAt) {
374
- await this.pool.query("UPDATE projects SET slug = $1, updated_at = $2 WHERE id = $3", [
375
- slug,
376
- updatedAt,
377
- id
378
- ]);
379
- }
380
- async reassignProjectParent(fromParentId, toParentId) {
381
- return (await this.pool.query("UPDATE projects SET parent_id = $1 WHERE parent_id = $2", [toParentId, fromParentId])).rowCount ?? 0;
382
- }
383
- async deleteProject(id) {
384
- await this.pool.query("DELETE FROM projects WHERE id = $1", [id]);
385
- }
386
- async deleteProjectCascade(id) {
387
- const client = await this.pool.connect();
388
- try {
389
- await client.query("BEGIN");
390
- await client.query("DELETE FROM links WHERE target_project_id = $1", [id]);
391
- await client.query("DELETE FROM links WHERE session_id IN (SELECT id FROM sessions WHERE project_id = $1)", [id]);
392
- await client.query("DELETE FROM compaction_log WHERE project_id = $1", [id]);
393
- await client.query("DELETE FROM project_tags WHERE project_id = $1", [id]);
394
- await client.query("DELETE FROM aliases WHERE project_id = $1", [id]);
395
- await client.query("DELETE FROM sessions WHERE project_id = $1", [id]);
396
- await client.query("DELETE FROM projects WHERE id = $1", [id]);
397
- await client.query("COMMIT");
398
- } catch (e) {
399
- await client.query("ROLLBACK");
400
- throw e;
401
- } finally {
402
- client.release();
403
- }
404
- }
405
- async listTagsForProject(projectId) {
406
- return (await this.pool.query("SELECT t.name FROM tags t JOIN project_tags pt ON pt.tag_id = t.id WHERE pt.project_id = $1 ORDER BY t.name", [projectId])).rows.map((row) => row.name);
407
- }
408
- async listAllTags() {
409
- return (await this.pool.query("SELECT id, name FROM tags ORDER BY name")).rows;
410
- }
411
- async upsertTag(name) {
412
- await this.pool.query("INSERT INTO tags (name) VALUES ($1) ON CONFLICT (name) DO NOTHING", [name]);
413
- return (await this.pool.query("SELECT id FROM tags WHERE name = $1", [name])).rows[0].id;
414
- }
415
- async addProjectTag(projectId, tagId) {
416
- await this.pool.query("INSERT INTO project_tags (project_id, tag_id) VALUES ($1, $2)", [projectId, tagId]);
417
- }
418
- async projectHasTag(projectId, tagId) {
419
- return ((await this.pool.query("SELECT 1 FROM project_tags WHERE project_id = $1 AND tag_id = $2", [projectId, tagId])).rowCount ?? 0) > 0;
420
- }
421
- async deleteProjectTag(projectId, tagId) {
422
- await this.pool.query("DELETE FROM project_tags WHERE project_id = $1 AND tag_id = $2", [projectId, tagId]);
423
- }
424
- async reassignProjectTag(fromProjectId, toProjectId, tagId) {
425
- await this.pool.query("UPDATE project_tags SET project_id = $1 WHERE project_id = $2 AND tag_id = $3", [
426
- toProjectId,
427
- fromProjectId,
428
- tagId
429
- ]);
430
- }
431
- async listProjectTagIds(projectId) {
432
- return (await this.pool.query("SELECT tag_id FROM project_tags WHERE project_id = $1", [projectId])).rows.map((row) => row.tag_id);
433
- }
434
- async countProjectTagsForProject(projectId) {
435
- const r = await this.pool.query("SELECT COUNT(*)::text AS n FROM project_tags WHERE project_id = $1", [projectId]);
436
- return parseInt(r.rows[0].n, 10);
437
- }
438
- async copyProjectTags(fromProjectId, toProjectId) {
439
- await this.pool.query(`INSERT INTO project_tags (project_id, tag_id) SELECT $1, tag_id FROM project_tags WHERE project_id = $2
440
- ON CONFLICT (project_id, tag_id) DO NOTHING`, [toProjectId, fromProjectId]);
441
- }
442
- async deleteProjectTagsForProject(projectId) {
443
- await this.pool.query("DELETE FROM project_tags WHERE project_id = $1", [projectId]);
444
- }
445
- async resolveAlias(alias) {
446
- return (await this.pool.query("SELECT project_id FROM aliases WHERE alias = $1", [alias])).rows[0]?.project_id ?? null;
447
- }
448
- async listAliasesForProject(projectId) {
449
- return (await this.pool.query("SELECT alias FROM aliases WHERE project_id = $1 ORDER BY alias", [projectId])).rows.map((row) => row.alias);
450
- }
451
- async addAlias(alias, projectId) {
452
- await this.pool.query("INSERT INTO aliases (alias, project_id) VALUES ($1, $2)", [alias, projectId]);
453
- }
454
- async removeAlias(alias) {
455
- await this.pool.query("DELETE FROM aliases WHERE alias = $1", [alias]);
456
- }
457
- async countAliasesForProject(projectId) {
458
- const r = await this.pool.query("SELECT COUNT(*)::text AS n FROM aliases WHERE project_id = $1", [projectId]);
459
- return parseInt(r.rows[0].n, 10);
460
- }
461
- async moveProjectAliases(fromProjectId, toProjectId) {
462
- await this.pool.query("UPDATE aliases SET project_id = $1 WHERE project_id = $2", [toProjectId, fromProjectId]);
463
- }
464
- async reassignAlias(alias, toProjectId) {
465
- await this.pool.query("UPDATE aliases SET project_id = $1 WHERE alias = $2", [toProjectId, alias]);
466
- }
467
- async listAliasMap() {
468
- return (await this.pool.query(`SELECT a.alias AS alias, p.slug AS slug, p.root_path AS root_path
469
- FROM aliases a JOIN projects p ON p.id = a.project_id WHERE p.status != 'archived'`)).rows;
470
- }
471
- async getSessionById(id) {
472
- const r = await this.pool.query("SELECT * FROM sessions WHERE id = $1", [id]);
473
- return r.rows[0] ? mapSession(r.rows[0]) : null;
474
- }
475
- async getSessionByNumber(projectId, number) {
476
- const r = await this.pool.query("SELECT * FROM sessions WHERE project_id = $1 AND number = $2", [projectId, number]);
477
- return r.rows[0] ? mapSession(r.rows[0]) : null;
478
- }
479
- async getLatestSessionForProject(projectId) {
480
- const r = await this.pool.query("SELECT * FROM sessions WHERE project_id = $1 ORDER BY number DESC LIMIT 1", [projectId]);
481
- return r.rows[0] ? mapSession(r.rows[0]) : null;
482
- }
483
- async getMaxSessionNumber(projectId) {
484
- return (await this.pool.query("SELECT COALESCE(MAX(number), 0) AS n FROM sessions WHERE project_id = $1", [projectId])).rows[0].n;
485
- }
486
- async listSessionsForProject(projectId, opts = {}) {
487
- let sql = `SELECT * FROM sessions WHERE project_id = $1 ORDER BY ${opts.orderBy === "created_desc" ? "created_at DESC" : "number ASC"}`;
488
- const params = [projectId];
489
- if (opts.limit !== void 0) {
490
- params.push(opts.limit);
491
- sql += ` LIMIT $${params.length}`;
492
- }
493
- return (await this.pool.query(sql, params)).rows.map(mapSession);
494
- }
495
- async listSessions(opts = {}) {
496
- const where = [];
497
- const params = [];
498
- if (opts.projectId !== void 0) {
499
- params.push(opts.projectId);
500
- where.push(`s.project_id = $${params.length}`);
501
- }
502
- if (opts.status) {
503
- params.push(opts.status);
504
- where.push(`s.status = $${params.length}`);
505
- }
506
- let sql = `
507
- SELECT s.*, p.slug AS project_slug, p.display_name AS project_name
508
- FROM sessions s JOIN projects p ON p.id = s.project_id
509
- `;
510
- if (where.length) sql += " WHERE " + where.join(" AND ");
511
- sql += " ORDER BY s.date DESC, s.number DESC";
512
- params.push(opts.limit ?? 20);
513
- sql += ` LIMIT $${params.length}`;
514
- return (await this.pool.query(sql, params)).rows.map((row) => ({
515
- ...mapSession(row),
516
- project_slug: row.project_slug,
517
- project_name: row.project_name
518
- }));
519
- }
520
- async findSessionByFilename(projectId, filename) {
521
- return (await this.pool.query("SELECT id FROM sessions WHERE project_id = $1 AND filename = $2 LIMIT 1", [projectId, filename])).rows[0] ?? null;
522
- }
523
- async sessionNumberTaken(projectId, number) {
524
- return ((await this.pool.query("SELECT 1 FROM sessions WHERE project_id = $1 AND number = $2 LIMIT 1", [projectId, number])).rowCount ?? 0) > 0;
525
- }
526
- async createSession(input) {
527
- const status = input.status ?? "completed";
528
- return mapSession((await this.pool.query(`INSERT INTO sessions (project_id, number, date, slug, title, filename, status, created_at)
529
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *`, [
530
- input.projectId,
531
- input.number,
532
- input.date,
533
- input.slug,
534
- input.title,
535
- input.filename,
536
- status,
537
- input.createdAt
538
- ])).rows[0]);
539
- }
540
- async upsertSessionIfAbsent(input) {
541
- if (((await this.pool.query("SELECT id FROM sessions WHERE project_id = $1 AND number = $2", [input.projectId, input.number])).rowCount ?? 0) > 0) return false;
542
- const status = input.status ?? "completed";
543
- await this.pool.query(`INSERT INTO sessions (project_id, number, date, slug, title, filename, status, created_at)
544
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, [
545
- input.projectId,
546
- input.number,
547
- input.date,
548
- input.slug,
549
- input.title,
550
- input.filename,
551
- status,
552
- input.createdAt
553
- ]);
554
- return true;
555
- }
556
- async updateSessionMeta(id, patch) {
557
- const sets = [];
558
- const params = [];
559
- if (patch.slug !== void 0) {
560
- params.push(patch.slug);
561
- sets.push(`slug = $${params.length}`);
562
- }
563
- if (patch.title !== void 0) {
564
- params.push(patch.title);
565
- sets.push(`title = $${params.length}`);
566
- }
567
- if (patch.filename !== void 0) {
568
- params.push(patch.filename);
569
- sets.push(`filename = $${params.length}`);
570
- }
571
- if (!sets.length) return;
572
- params.push(id);
573
- await this.pool.query(`UPDATE sessions SET ${sets.join(", ")} WHERE id = $${params.length}`, params);
574
- }
575
- async updateSessionNumber(id, number, opts = {}) {
576
- if (opts.filename !== void 0) await this.pool.query("UPDATE sessions SET number = $1, filename = $2 WHERE id = $3", [
577
- number,
578
- opts.filename,
579
- id
580
- ]);
581
- else await this.pool.query("UPDATE sessions SET number = $1 WHERE id = $2", [number, id]);
582
- }
583
- async updateSessionFilename(id, filename) {
584
- await this.pool.query("UPDATE sessions SET filename = $1 WHERE id = $2", [filename, id]);
585
- }
586
- async updateSessionStatus(id, status, opts = {}) {
587
- if (opts.closedAt !== void 0) await this.pool.query("UPDATE sessions SET status = $1, closed_at = $2 WHERE id = $3", [
588
- status,
589
- opts.closedAt,
590
- id
591
- ]);
592
- else await this.pool.query("UPDATE sessions SET status = $1 WHERE id = $2", [status, id]);
593
- }
594
- async moveSessionToProject(id, projectId, opts = {}) {
595
- if (opts.number !== void 0) await this.pool.query("UPDATE sessions SET project_id = $1, number = $2 WHERE id = $3", [
596
- projectId,
597
- opts.number,
598
- id
599
- ]);
600
- else await this.pool.query("UPDATE sessions SET project_id = $1 WHERE id = $2", [projectId, id]);
601
- }
602
- async deleteSession(id) {
603
- await this.pool.query("DELETE FROM sessions WHERE id = $1", [id]);
604
- }
605
- async backfillFoldedSession(keepId, dropId) {
606
- await this.pool.query(`UPDATE sessions SET
607
- claude_session_id = COALESCE(claude_session_id,
608
- (SELECT claude_session_id FROM sessions WHERE id = $1)),
609
- token_count = COALESCE(token_count,
610
- (SELECT token_count FROM sessions WHERE id = $1)),
611
- closed_at = COALESCE(closed_at,
612
- (SELECT closed_at FROM sessions WHERE id = $1)),
613
- status = CASE WHEN status = 'open'
614
- AND (SELECT status FROM sessions WHERE id = $1) != 'open'
615
- THEN (SELECT status FROM sessions WHERE id = $1)
616
- ELSE status END
617
- WHERE id = $2`, [dropId, keepId]);
618
- }
619
- async countSessionsForProject(projectId) {
620
- const r = await this.pool.query("SELECT COUNT(*)::text AS n FROM sessions WHERE project_id = $1", [projectId]);
621
- return parseInt(r.rows[0].n, 10);
622
- }
623
- async countSessions() {
624
- const r = await this.pool.query("SELECT COUNT(*)::text AS n FROM sessions");
625
- return parseInt(r.rows[0].n, 10);
626
- }
627
- async getMostRecentSessionDate(projectId) {
628
- return (await this.pool.query("SELECT date FROM sessions WHERE project_id = $1 ORDER BY date DESC LIMIT 1", [projectId])).rows[0]?.date ?? null;
629
- }
630
- async getMostRecentSessionCreatedAt() {
631
- const r = await this.pool.query("SELECT created_at FROM sessions ORDER BY created_at DESC LIMIT 1");
632
- return r.rows[0] ? Number(r.rows[0].created_at) : null;
633
- }
634
- async listSessionTagIds(sessionId) {
635
- return (await this.pool.query("SELECT tag_id FROM session_tags WHERE session_id = $1", [sessionId])).rows.map((row) => row.tag_id);
636
- }
637
- async sessionHasTag(sessionId, tagId) {
638
- return ((await this.pool.query("SELECT 1 FROM session_tags WHERE session_id = $1 AND tag_id = $2", [sessionId, tagId])).rowCount ?? 0) > 0;
639
- }
640
- async addSessionTag(sessionId, tagId) {
641
- await this.pool.query("INSERT INTO session_tags (session_id, tag_id) VALUES ($1, $2)", [sessionId, tagId]);
642
- }
643
- async deleteSessionTag(sessionId, tagId) {
644
- await this.pool.query("DELETE FROM session_tags WHERE session_id = $1 AND tag_id = $2", [sessionId, tagId]);
645
- }
646
- async reassignSessionTag(fromSessionId, toSessionId, tagId) {
647
- await this.pool.query("UPDATE session_tags SET session_id = $1 WHERE session_id = $2 AND tag_id = $3", [
648
- toSessionId,
649
- fromSessionId,
650
- tagId
651
- ]);
652
- }
653
- async listTagsForSession(sessionId) {
654
- return (await this.pool.query("SELECT t.name FROM tags t JOIN session_tags st ON st.tag_id = t.id WHERE st.session_id = $1 ORDER BY t.name", [sessionId])).rows.map((row) => row.name);
655
- }
656
- async addLink(input) {
657
- await this.pool.query("INSERT INTO links (session_id, target_project_id, link_type, created_at) VALUES ($1, $2, $3, $4)", [
658
- input.sessionId,
659
- input.targetProjectId,
660
- input.linkType,
661
- input.createdAt
662
- ]);
663
- }
664
- async listLinksForSession(sessionId) {
665
- return (await this.pool.query("SELECT id, target_project_id FROM links WHERE session_id = $1", [sessionId])).rows;
666
- }
667
- async listLinksForProject(projectId) {
668
- return (await this.pool.query("SELECT id, session_id FROM links WHERE target_project_id = $1", [projectId])).rows;
669
- }
670
- async linkExists(sessionId, targetProjectId) {
671
- return ((await this.pool.query("SELECT 1 FROM links WHERE session_id = $1 AND target_project_id = $2 LIMIT 1", [sessionId, targetProjectId])).rowCount ?? 0) > 0;
672
- }
673
- async deleteLink(id) {
674
- await this.pool.query("DELETE FROM links WHERE id = $1", [id]);
675
- }
676
- async deleteLinksTargetingProject(projectId) {
677
- await this.pool.query("DELETE FROM links WHERE target_project_id = $1", [projectId]);
678
- }
679
- async deleteLinksFromProjectSessions(projectId) {
680
- await this.pool.query("DELETE FROM links WHERE session_id IN (SELECT id FROM sessions WHERE project_id = $1)", [projectId]);
681
- }
682
- async retargetLink(id, targetProjectId) {
683
- await this.pool.query("UPDATE links SET target_project_id = $1 WHERE id = $2", [targetProjectId, id]);
684
- }
685
- async moveLinkToSession(id, sessionId) {
686
- await this.pool.query("UPDATE links SET session_id = $1 WHERE id = $2", [sessionId, id]);
687
- }
688
- async reassignLinksTarget(fromProjectId, toProjectId) {
689
- await this.pool.query(`UPDATE links SET target_project_id = $1 WHERE target_project_id = $2
690
- AND NOT EXISTS (
691
- SELECT 1 FROM links l2 WHERE l2.session_id = links.session_id AND l2.target_project_id = $1
692
- )`, [toProjectId, fromProjectId]);
693
- }
694
- async countLinksForProject(projectId) {
695
- const r = await this.pool.query("SELECT COUNT(*)::text AS n FROM links WHERE target_project_id = $1", [projectId]);
696
- return parseInt(r.rows[0].n, 10);
697
- }
698
- async deleteSelfLinksForProject(projectId) {
699
- await this.pool.query(`DELETE FROM links WHERE target_project_id = $1
700
- AND session_id IN (SELECT id FROM sessions WHERE project_id = $2)`, [projectId, projectId]);
701
- }
702
- async appendCompactionLog(entry) {
703
- await this.pool.query(`INSERT INTO compaction_log (project_id, session_id, trigger, files_written, token_count, created_at)
704
- VALUES ($1, $2, $3, $4, $5, $6)`, [
705
- entry.projectId,
706
- entry.sessionId,
707
- entry.trigger,
708
- entry.filesWritten,
709
- entry.tokenCount,
710
- entry.createdAt
711
- ]);
712
- }
713
- async countCompactionLogsForProject(projectId) {
714
- const r = await this.pool.query("SELECT COUNT(*)::text AS n FROM compaction_log WHERE project_id = $1", [projectId]);
715
- return parseInt(r.rows[0].n, 10);
716
- }
717
- async moveCompactionLogsByProject(fromProjectId, toProjectId) {
718
- return (await this.pool.query("UPDATE compaction_log SET project_id = $1 WHERE project_id = $2", [toProjectId, fromProjectId])).rowCount ?? 0;
719
- }
720
- async moveCompactionLogsBySession(fromSessionId, toSessionId) {
721
- await this.pool.query("UPDATE compaction_log SET session_id = $1 WHERE session_id = $2", [toSessionId, fromSessionId]);
722
- }
723
- async deleteCompactionLogsForProject(projectId) {
724
- await this.pool.query("DELETE FROM compaction_log WHERE project_id = $1", [projectId]);
725
- }
726
- async planProjectMerge(fromSlug, intoSlug) {
727
- const { MergeError } = await import("./merge-2gqRPFu2.mjs");
728
- if (fromSlug === intoSlug) throw new MergeError(`Cannot merge ${fromSlug} into itself.`);
729
- const fromR = await this.pool.query("SELECT id, slug FROM projects WHERE slug = $1", [fromSlug]);
730
- const intoR = await this.pool.query("SELECT id, slug FROM projects WHERE slug = $1", [intoSlug]);
731
- const from = fromR.rows[0];
732
- const into = intoR.rows[0];
733
- if (!from) throw new MergeError(`No project with slug "${fromSlug}".`);
734
- if (!into) throw new MergeError(`No project with slug "${intoSlug}".`);
735
- let next = (await this.pool.query("SELECT COALESCE(MAX(number), 0) AS n FROM sessions WHERE project_id = $1", [into.id])).rows[0].n;
736
- const sessions = (await this.pool.query("SELECT id, number FROM sessions WHERE project_id = $1 ORDER BY number ASC", [from.id])).rows.map((s) => ({
737
- id: s.id,
738
- from: s.number,
739
- to: ++next
740
- }));
741
- const count = async (sql, ...args) => {
742
- const r = await this.pool.query(sql, args);
743
- return parseInt(r.rows[0].n, 10);
744
- };
745
- const aliasTaken = await count("SELECT COUNT(*)::text AS n FROM aliases WHERE alias = $1", from.slug) > 0 || await count("SELECT COUNT(*)::text AS n FROM projects WHERE slug = $1", from.slug) > 1;
746
- return {
747
- fromId: from.id,
748
- fromSlug: from.slug,
749
- intoId: into.id,
750
- intoSlug: into.slug,
751
- sessions,
752
- tags: await count("SELECT COUNT(*)::text AS n FROM project_tags WHERE project_id = $1", from.id),
753
- aliases: await count("SELECT COUNT(*)::text AS n FROM aliases WHERE project_id = $1", from.id),
754
- compactions: await count("SELECT COUNT(*)::text AS n FROM compaction_log WHERE project_id = $1", from.id),
755
- links: await count("SELECT COUNT(*)::text AS n FROM links WHERE target_project_id = $1", from.id),
756
- aliasToAdd: aliasTaken ? void 0 : from.slug
757
- };
758
- }
759
- async applyProjectMerge(plan) {
760
- const client = await this.pool.connect();
761
- try {
762
- await client.query("BEGIN");
763
- for (const s of plan.sessions) await client.query("UPDATE sessions SET project_id = $1, number = $2 WHERE id = $3", [
764
- plan.intoId,
765
- s.to,
766
- s.id
767
- ]);
768
- await client.query(`INSERT INTO project_tags (project_id, tag_id)
769
- SELECT $1, tag_id FROM project_tags WHERE project_id = $2
770
- ON CONFLICT (project_id, tag_id) DO NOTHING`, [plan.intoId, plan.fromId]);
771
- await client.query("DELETE FROM project_tags WHERE project_id = $1", [plan.fromId]);
772
- await client.query("UPDATE aliases SET project_id = $1 WHERE project_id = $2", [plan.intoId, plan.fromId]);
773
- await client.query("UPDATE compaction_log SET project_id = $1 WHERE project_id = $2", [plan.intoId, plan.fromId]);
774
- await client.query(`UPDATE links SET target_project_id = $1 WHERE target_project_id = $2
775
- AND NOT EXISTS (
776
- SELECT 1 FROM links l2 WHERE l2.session_id = links.session_id AND l2.target_project_id = $1
777
- )`, [plan.intoId, plan.fromId]);
778
- await client.query("DELETE FROM links WHERE target_project_id = $1", [plan.fromId]);
779
- await client.query(`DELETE FROM links WHERE target_project_id = $1
780
- AND session_id IN (SELECT id FROM sessions WHERE project_id = $2)`, [plan.intoId, plan.intoId]);
781
- if (plan.aliasToAdd) await client.query("INSERT INTO aliases (alias, project_id) VALUES ($1, $2) ON CONFLICT (alias) DO NOTHING", [plan.aliasToAdd, plan.intoId]);
782
- await client.query("DELETE FROM projects WHERE id = $1", [plan.fromId]);
783
- await client.query("COMMIT");
784
- } catch (e) {
785
- await client.query("ROLLBACK");
786
- throw e;
787
- } finally {
788
- client.release();
789
- }
790
- }
791
- };
792
-
793
- //#endregion
794
- export { PostgresRegistryBackend };
795
- //# sourceMappingURL=registry-postgres-nOfyBSIW.mjs.map