@agent-relay/cloud 0.1.0

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 (269) hide show
  1. package/dist/api/admin.d.ts +8 -0
  2. package/dist/api/admin.d.ts.map +1 -0
  3. package/dist/api/admin.js +225 -0
  4. package/dist/api/admin.js.map +1 -0
  5. package/dist/api/auth.d.ts +20 -0
  6. package/dist/api/auth.d.ts.map +1 -0
  7. package/dist/api/auth.js +136 -0
  8. package/dist/api/auth.js.map +1 -0
  9. package/dist/api/billing.d.ts +7 -0
  10. package/dist/api/billing.d.ts.map +1 -0
  11. package/dist/api/billing.js +564 -0
  12. package/dist/api/billing.js.map +1 -0
  13. package/dist/api/cli-pty-runner.d.ts +53 -0
  14. package/dist/api/cli-pty-runner.d.ts.map +1 -0
  15. package/dist/api/cli-pty-runner.js +193 -0
  16. package/dist/api/cli-pty-runner.js.map +1 -0
  17. package/dist/api/codex-auth-helper.d.ts +21 -0
  18. package/dist/api/codex-auth-helper.d.ts.map +1 -0
  19. package/dist/api/codex-auth-helper.js +327 -0
  20. package/dist/api/codex-auth-helper.js.map +1 -0
  21. package/dist/api/consensus.d.ts +13 -0
  22. package/dist/api/consensus.d.ts.map +1 -0
  23. package/dist/api/consensus.js +261 -0
  24. package/dist/api/consensus.js.map +1 -0
  25. package/dist/api/coordinators.d.ts +8 -0
  26. package/dist/api/coordinators.d.ts.map +1 -0
  27. package/dist/api/coordinators.js +750 -0
  28. package/dist/api/coordinators.js.map +1 -0
  29. package/dist/api/daemons.d.ts +12 -0
  30. package/dist/api/daemons.d.ts.map +1 -0
  31. package/dist/api/daemons.js +535 -0
  32. package/dist/api/daemons.js.map +1 -0
  33. package/dist/api/generic-webhooks.d.ts +8 -0
  34. package/dist/api/generic-webhooks.d.ts.map +1 -0
  35. package/dist/api/generic-webhooks.js +129 -0
  36. package/dist/api/generic-webhooks.js.map +1 -0
  37. package/dist/api/git.d.ts +8 -0
  38. package/dist/api/git.d.ts.map +1 -0
  39. package/dist/api/git.js +269 -0
  40. package/dist/api/git.js.map +1 -0
  41. package/dist/api/github-app.d.ts +11 -0
  42. package/dist/api/github-app.d.ts.map +1 -0
  43. package/dist/api/github-app.js +223 -0
  44. package/dist/api/github-app.js.map +1 -0
  45. package/dist/api/middleware/planLimits.d.ts +43 -0
  46. package/dist/api/middleware/planLimits.d.ts.map +1 -0
  47. package/dist/api/middleware/planLimits.js +202 -0
  48. package/dist/api/middleware/planLimits.js.map +1 -0
  49. package/dist/api/monitoring.d.ts +11 -0
  50. package/dist/api/monitoring.d.ts.map +1 -0
  51. package/dist/api/monitoring.js +578 -0
  52. package/dist/api/monitoring.js.map +1 -0
  53. package/dist/api/nango-auth.d.ts +9 -0
  54. package/dist/api/nango-auth.d.ts.map +1 -0
  55. package/dist/api/nango-auth.js +674 -0
  56. package/dist/api/nango-auth.js.map +1 -0
  57. package/dist/api/onboarding.d.ts +15 -0
  58. package/dist/api/onboarding.d.ts.map +1 -0
  59. package/dist/api/onboarding.js +679 -0
  60. package/dist/api/onboarding.js.map +1 -0
  61. package/dist/api/policy.d.ts +8 -0
  62. package/dist/api/policy.d.ts.map +1 -0
  63. package/dist/api/policy.js +229 -0
  64. package/dist/api/policy.js.map +1 -0
  65. package/dist/api/provider-env.d.ts +14 -0
  66. package/dist/api/provider-env.d.ts.map +1 -0
  67. package/dist/api/provider-env.js +75 -0
  68. package/dist/api/provider-env.js.map +1 -0
  69. package/dist/api/providers.d.ts +7 -0
  70. package/dist/api/providers.d.ts.map +1 -0
  71. package/dist/api/providers.js +564 -0
  72. package/dist/api/providers.js.map +1 -0
  73. package/dist/api/repos.d.ts +8 -0
  74. package/dist/api/repos.d.ts.map +1 -0
  75. package/dist/api/repos.js +577 -0
  76. package/dist/api/repos.js.map +1 -0
  77. package/dist/api/sessions.d.ts +11 -0
  78. package/dist/api/sessions.d.ts.map +1 -0
  79. package/dist/api/sessions.js +302 -0
  80. package/dist/api/sessions.js.map +1 -0
  81. package/dist/api/teams.d.ts +7 -0
  82. package/dist/api/teams.d.ts.map +1 -0
  83. package/dist/api/teams.js +281 -0
  84. package/dist/api/teams.js.map +1 -0
  85. package/dist/api/test-helpers.d.ts +10 -0
  86. package/dist/api/test-helpers.d.ts.map +1 -0
  87. package/dist/api/test-helpers.js +745 -0
  88. package/dist/api/test-helpers.js.map +1 -0
  89. package/dist/api/usage.d.ts +7 -0
  90. package/dist/api/usage.d.ts.map +1 -0
  91. package/dist/api/usage.js +111 -0
  92. package/dist/api/usage.js.map +1 -0
  93. package/dist/api/webhooks.d.ts +8 -0
  94. package/dist/api/webhooks.d.ts.map +1 -0
  95. package/dist/api/webhooks.js +645 -0
  96. package/dist/api/webhooks.js.map +1 -0
  97. package/dist/api/workspaces.d.ts +25 -0
  98. package/dist/api/workspaces.d.ts.map +1 -0
  99. package/dist/api/workspaces.js +1799 -0
  100. package/dist/api/workspaces.js.map +1 -0
  101. package/dist/billing/index.d.ts +9 -0
  102. package/dist/billing/index.d.ts.map +1 -0
  103. package/dist/billing/index.js +9 -0
  104. package/dist/billing/index.js.map +1 -0
  105. package/dist/billing/plans.d.ts +39 -0
  106. package/dist/billing/plans.d.ts.map +1 -0
  107. package/dist/billing/plans.js +245 -0
  108. package/dist/billing/plans.js.map +1 -0
  109. package/dist/billing/service.d.ts +80 -0
  110. package/dist/billing/service.d.ts.map +1 -0
  111. package/dist/billing/service.js +388 -0
  112. package/dist/billing/service.js.map +1 -0
  113. package/dist/billing/types.d.ts +141 -0
  114. package/dist/billing/types.d.ts.map +1 -0
  115. package/dist/billing/types.js +7 -0
  116. package/dist/billing/types.js.map +1 -0
  117. package/dist/config.d.ts +5 -0
  118. package/dist/config.d.ts.map +1 -0
  119. package/dist/config.js +5 -0
  120. package/dist/config.js.map +1 -0
  121. package/dist/db/bulk-ingest.d.ts +89 -0
  122. package/dist/db/bulk-ingest.d.ts.map +1 -0
  123. package/dist/db/bulk-ingest.js +268 -0
  124. package/dist/db/bulk-ingest.js.map +1 -0
  125. package/dist/db/drizzle.d.ts +256 -0
  126. package/dist/db/drizzle.d.ts.map +1 -0
  127. package/dist/db/drizzle.js +1286 -0
  128. package/dist/db/drizzle.js.map +1 -0
  129. package/dist/db/index.d.ts +55 -0
  130. package/dist/db/index.d.ts.map +1 -0
  131. package/dist/db/index.js +68 -0
  132. package/dist/db/index.js.map +1 -0
  133. package/dist/db/schema.d.ts +4873 -0
  134. package/dist/db/schema.d.ts.map +1 -0
  135. package/dist/db/schema.js +620 -0
  136. package/dist/db/schema.js.map +1 -0
  137. package/dist/index.d.ts +11 -0
  138. package/dist/index.d.ts.map +1 -0
  139. package/dist/index.js +38 -0
  140. package/dist/index.js.map +1 -0
  141. package/dist/provisioner/index.d.ts +207 -0
  142. package/dist/provisioner/index.d.ts.map +1 -0
  143. package/dist/provisioner/index.js +2114 -0
  144. package/dist/provisioner/index.js.map +1 -0
  145. package/dist/server.d.ts +17 -0
  146. package/dist/server.d.ts.map +1 -0
  147. package/dist/server.js +1924 -0
  148. package/dist/server.js.map +1 -0
  149. package/dist/services/auto-scaler.d.ts +152 -0
  150. package/dist/services/auto-scaler.d.ts.map +1 -0
  151. package/dist/services/auto-scaler.js +439 -0
  152. package/dist/services/auto-scaler.js.map +1 -0
  153. package/dist/services/capacity-manager.d.ts +148 -0
  154. package/dist/services/capacity-manager.d.ts.map +1 -0
  155. package/dist/services/capacity-manager.js +449 -0
  156. package/dist/services/capacity-manager.js.map +1 -0
  157. package/dist/services/ci-agent-spawner.d.ts +49 -0
  158. package/dist/services/ci-agent-spawner.d.ts.map +1 -0
  159. package/dist/services/ci-agent-spawner.js +373 -0
  160. package/dist/services/ci-agent-spawner.js.map +1 -0
  161. package/dist/services/cloud-message-bus.d.ts +28 -0
  162. package/dist/services/cloud-message-bus.d.ts.map +1 -0
  163. package/dist/services/cloud-message-bus.js +19 -0
  164. package/dist/services/cloud-message-bus.js.map +1 -0
  165. package/dist/services/compute-enforcement.d.ts +57 -0
  166. package/dist/services/compute-enforcement.d.ts.map +1 -0
  167. package/dist/services/compute-enforcement.js +175 -0
  168. package/dist/services/compute-enforcement.js.map +1 -0
  169. package/dist/services/coordinator.d.ts +62 -0
  170. package/dist/services/coordinator.d.ts.map +1 -0
  171. package/dist/services/coordinator.js +389 -0
  172. package/dist/services/coordinator.js.map +1 -0
  173. package/dist/services/index.d.ts +17 -0
  174. package/dist/services/index.d.ts.map +1 -0
  175. package/dist/services/index.js +25 -0
  176. package/dist/services/index.js.map +1 -0
  177. package/dist/services/intro-expiration.d.ts +60 -0
  178. package/dist/services/intro-expiration.d.ts.map +1 -0
  179. package/dist/services/intro-expiration.js +252 -0
  180. package/dist/services/intro-expiration.js.map +1 -0
  181. package/dist/services/mention-handler.d.ts +65 -0
  182. package/dist/services/mention-handler.d.ts.map +1 -0
  183. package/dist/services/mention-handler.js +405 -0
  184. package/dist/services/mention-handler.js.map +1 -0
  185. package/dist/services/nango.d.ts +201 -0
  186. package/dist/services/nango.d.ts.map +1 -0
  187. package/dist/services/nango.js +392 -0
  188. package/dist/services/nango.js.map +1 -0
  189. package/dist/services/persistence.d.ts +131 -0
  190. package/dist/services/persistence.d.ts.map +1 -0
  191. package/dist/services/persistence.js +200 -0
  192. package/dist/services/persistence.js.map +1 -0
  193. package/dist/services/planLimits.d.ts +147 -0
  194. package/dist/services/planLimits.d.ts.map +1 -0
  195. package/dist/services/planLimits.js +335 -0
  196. package/dist/services/planLimits.js.map +1 -0
  197. package/dist/services/presence-registry.d.ts +56 -0
  198. package/dist/services/presence-registry.d.ts.map +1 -0
  199. package/dist/services/presence-registry.js +91 -0
  200. package/dist/services/presence-registry.js.map +1 -0
  201. package/dist/services/scaling-orchestrator.d.ts +159 -0
  202. package/dist/services/scaling-orchestrator.d.ts.map +1 -0
  203. package/dist/services/scaling-orchestrator.js +502 -0
  204. package/dist/services/scaling-orchestrator.js.map +1 -0
  205. package/dist/services/scaling-policy.d.ts +121 -0
  206. package/dist/services/scaling-policy.d.ts.map +1 -0
  207. package/dist/services/scaling-policy.js +415 -0
  208. package/dist/services/scaling-policy.js.map +1 -0
  209. package/dist/services/ssh-security.d.ts +31 -0
  210. package/dist/services/ssh-security.d.ts.map +1 -0
  211. package/dist/services/ssh-security.js +63 -0
  212. package/dist/services/ssh-security.js.map +1 -0
  213. package/dist/services/workspace-keepalive.d.ts +76 -0
  214. package/dist/services/workspace-keepalive.d.ts.map +1 -0
  215. package/dist/services/workspace-keepalive.js +234 -0
  216. package/dist/services/workspace-keepalive.js.map +1 -0
  217. package/dist/shims/consensus.d.ts +23 -0
  218. package/dist/shims/consensus.d.ts.map +1 -0
  219. package/dist/shims/consensus.js +5 -0
  220. package/dist/shims/consensus.js.map +1 -0
  221. package/dist/webhooks/index.d.ts +24 -0
  222. package/dist/webhooks/index.d.ts.map +1 -0
  223. package/dist/webhooks/index.js +29 -0
  224. package/dist/webhooks/index.js.map +1 -0
  225. package/dist/webhooks/parsers/github.d.ts +8 -0
  226. package/dist/webhooks/parsers/github.d.ts.map +1 -0
  227. package/dist/webhooks/parsers/github.js +234 -0
  228. package/dist/webhooks/parsers/github.js.map +1 -0
  229. package/dist/webhooks/parsers/index.d.ts +23 -0
  230. package/dist/webhooks/parsers/index.d.ts.map +1 -0
  231. package/dist/webhooks/parsers/index.js +30 -0
  232. package/dist/webhooks/parsers/index.js.map +1 -0
  233. package/dist/webhooks/parsers/linear.d.ts +9 -0
  234. package/dist/webhooks/parsers/linear.d.ts.map +1 -0
  235. package/dist/webhooks/parsers/linear.js +258 -0
  236. package/dist/webhooks/parsers/linear.js.map +1 -0
  237. package/dist/webhooks/parsers/slack.d.ts +9 -0
  238. package/dist/webhooks/parsers/slack.d.ts.map +1 -0
  239. package/dist/webhooks/parsers/slack.js +214 -0
  240. package/dist/webhooks/parsers/slack.js.map +1 -0
  241. package/dist/webhooks/responders/github.d.ts +8 -0
  242. package/dist/webhooks/responders/github.d.ts.map +1 -0
  243. package/dist/webhooks/responders/github.js +73 -0
  244. package/dist/webhooks/responders/github.js.map +1 -0
  245. package/dist/webhooks/responders/index.d.ts +23 -0
  246. package/dist/webhooks/responders/index.d.ts.map +1 -0
  247. package/dist/webhooks/responders/index.js +30 -0
  248. package/dist/webhooks/responders/index.js.map +1 -0
  249. package/dist/webhooks/responders/linear.d.ts +9 -0
  250. package/dist/webhooks/responders/linear.d.ts.map +1 -0
  251. package/dist/webhooks/responders/linear.js +149 -0
  252. package/dist/webhooks/responders/linear.js.map +1 -0
  253. package/dist/webhooks/responders/slack.d.ts +20 -0
  254. package/dist/webhooks/responders/slack.d.ts.map +1 -0
  255. package/dist/webhooks/responders/slack.js +178 -0
  256. package/dist/webhooks/responders/slack.js.map +1 -0
  257. package/dist/webhooks/router.d.ts +25 -0
  258. package/dist/webhooks/router.d.ts.map +1 -0
  259. package/dist/webhooks/router.js +504 -0
  260. package/dist/webhooks/router.js.map +1 -0
  261. package/dist/webhooks/rules-engine.d.ts +24 -0
  262. package/dist/webhooks/rules-engine.d.ts.map +1 -0
  263. package/dist/webhooks/rules-engine.js +287 -0
  264. package/dist/webhooks/rules-engine.js.map +1 -0
  265. package/dist/webhooks/types.d.ts +186 -0
  266. package/dist/webhooks/types.d.ts.map +1 -0
  267. package/dist/webhooks/types.js +8 -0
  268. package/dist/webhooks/types.js.map +1 -0
  269. package/package.json +55 -0
@@ -0,0 +1,1286 @@
1
+ /**
2
+ * Agent Relay Cloud - Drizzle Database Client
3
+ *
4
+ * Type-safe database access using Drizzle ORM.
5
+ * Use this instead of the raw pg client for new code.
6
+ */
7
+ import { drizzle } from 'drizzle-orm/node-postgres';
8
+ import { Pool } from 'pg';
9
+ import { eq, and, or, sql, desc, lt, isNull, isNotNull, inArray } from 'drizzle-orm';
10
+ import * as schema from './schema.js';
11
+ import { getConfig } from '../config.js';
12
+ import { DEFAULT_POOL_CONFIG } from './bulk-ingest.js';
13
+ // Re-export schema for direct table access
14
+ export * from './schema.js';
15
+ // Initialize pool and drizzle lazily
16
+ let pool = null;
17
+ let drizzleDb = null;
18
+ /**
19
+ * Get or create the connection pool with optimized settings.
20
+ * Pool configuration:
21
+ * - max: 20 connections (up from default 10)
22
+ * - idleTimeoutMillis: 30s (close idle connections)
23
+ * - connectionTimeoutMillis: 10s (fail fast on connection issues)
24
+ */
25
+ function getPool() {
26
+ if (!pool) {
27
+ const config = getConfig();
28
+ pool = new Pool({
29
+ connectionString: config.databaseUrl,
30
+ ...DEFAULT_POOL_CONFIG,
31
+ // Allow SSL for cloud databases
32
+ ssl: config.databaseUrl?.includes('sslmode=require')
33
+ ? { rejectUnauthorized: false }
34
+ : undefined,
35
+ });
36
+ // Log pool errors (connection issues, etc.)
37
+ pool.on('error', (err) => {
38
+ console.error('[db] Pool error:', err.message);
39
+ });
40
+ }
41
+ return pool;
42
+ }
43
+ /**
44
+ * Get the raw connection pool for bulk operations.
45
+ * Use this for optimized bulk inserts that bypass the ORM.
46
+ */
47
+ export function getRawPool() {
48
+ return getPool();
49
+ }
50
+ export function getDb() {
51
+ if (!drizzleDb) {
52
+ drizzleDb = drizzle(getPool(), { schema });
53
+ }
54
+ return drizzleDb;
55
+ }
56
+ export const userQueries = {
57
+ async findById(id) {
58
+ const db = getDb();
59
+ const result = await db.select().from(schema.users).where(eq(schema.users.id, id));
60
+ return result[0] ?? null;
61
+ },
62
+ async findByGithubId(githubId) {
63
+ const db = getDb();
64
+ const result = await db.select().from(schema.users).where(eq(schema.users.githubId, githubId));
65
+ return result[0] ?? null;
66
+ },
67
+ async findByGithubUsername(username) {
68
+ const db = getDb();
69
+ const result = await db.select().from(schema.users).where(eq(schema.users.githubUsername, username));
70
+ return result[0] ?? null;
71
+ },
72
+ async findByEmail(email) {
73
+ const db = getDb();
74
+ const result = await db.select().from(schema.users).where(eq(schema.users.email, email));
75
+ return result[0] ?? null;
76
+ },
77
+ async findByNangoConnectionId(connectionId) {
78
+ const db = getDb();
79
+ const result = await db
80
+ .select()
81
+ .from(schema.users)
82
+ .where(eq(schema.users.nangoConnectionId, connectionId));
83
+ return result[0] ?? null;
84
+ },
85
+ async findByIncomingConnectionId(connectionId) {
86
+ const db = getDb();
87
+ const result = await db.select().from(schema.users).where(eq(schema.users.incomingConnectionId, connectionId));
88
+ return result[0] ?? null;
89
+ },
90
+ async findByPlan(plan) {
91
+ const db = getDb();
92
+ const result = await db.select().from(schema.users).where(eq(schema.users.plan, plan));
93
+ return result;
94
+ },
95
+ async upsert(data) {
96
+ const db = getDb();
97
+ const result = await db
98
+ .insert(schema.users)
99
+ .values(data)
100
+ .onConflictDoUpdate({
101
+ target: schema.users.githubId,
102
+ set: {
103
+ githubUsername: data.githubUsername,
104
+ email: data.email,
105
+ avatarUrl: data.avatarUrl,
106
+ updatedAt: new Date(),
107
+ },
108
+ })
109
+ .returning();
110
+ return result[0];
111
+ },
112
+ async completeOnboarding(userId) {
113
+ const db = getDb();
114
+ await db
115
+ .update(schema.users)
116
+ .set({ onboardingCompletedAt: new Date(), updatedAt: new Date() })
117
+ .where(eq(schema.users.id, userId));
118
+ },
119
+ async update(id, data) {
120
+ const db = getDb();
121
+ await db
122
+ .update(schema.users)
123
+ .set({ ...data, updatedAt: new Date() })
124
+ .where(eq(schema.users.id, id));
125
+ },
126
+ async clearIncomingConnectionId(userId) {
127
+ const db = getDb();
128
+ await db
129
+ .update(schema.users)
130
+ .set({ incomingConnectionId: null, updatedAt: new Date() })
131
+ .where(eq(schema.users.id, userId));
132
+ },
133
+ async setPendingInstallationRequest(userId) {
134
+ const db = getDb();
135
+ await db
136
+ .update(schema.users)
137
+ .set({ pendingInstallationRequest: new Date(), updatedAt: new Date() })
138
+ .where(eq(schema.users.id, userId));
139
+ },
140
+ async clearPendingInstallationRequest(userId) {
141
+ const db = getDb();
142
+ await db
143
+ .update(schema.users)
144
+ .set({ pendingInstallationRequest: null, updatedAt: new Date() })
145
+ .where(eq(schema.users.id, userId));
146
+ },
147
+ };
148
+ export const githubInstallationQueries = {
149
+ async findById(id) {
150
+ const db = getDb();
151
+ const result = await db.select().from(schema.githubInstallations).where(eq(schema.githubInstallations.id, id));
152
+ return result[0] ?? null;
153
+ },
154
+ async findByInstallationId(installationId) {
155
+ const db = getDb();
156
+ const result = await db
157
+ .select()
158
+ .from(schema.githubInstallations)
159
+ .where(eq(schema.githubInstallations.installationId, installationId));
160
+ return result[0] ?? null;
161
+ },
162
+ async findByAccountLogin(accountLogin) {
163
+ const db = getDb();
164
+ const result = await db
165
+ .select()
166
+ .from(schema.githubInstallations)
167
+ .where(eq(schema.githubInstallations.accountLogin, accountLogin));
168
+ return result[0] ?? null;
169
+ },
170
+ async findByInstalledBy(userId) {
171
+ const db = getDb();
172
+ return db
173
+ .select()
174
+ .from(schema.githubInstallations)
175
+ .where(eq(schema.githubInstallations.installedById, userId));
176
+ },
177
+ async findAll() {
178
+ const db = getDb();
179
+ return db.select().from(schema.githubInstallations).orderBy(schema.githubInstallations.accountLogin);
180
+ },
181
+ async upsert(data) {
182
+ const db = getDb();
183
+ const result = await db
184
+ .insert(schema.githubInstallations)
185
+ .values(data)
186
+ .onConflictDoUpdate({
187
+ target: schema.githubInstallations.installationId,
188
+ set: {
189
+ accountType: data.accountType,
190
+ accountLogin: data.accountLogin,
191
+ accountId: data.accountId,
192
+ permissions: data.permissions,
193
+ events: data.events,
194
+ installedById: data.installedById,
195
+ updatedAt: new Date(),
196
+ },
197
+ })
198
+ .returning();
199
+ return result[0];
200
+ },
201
+ async updatePermissions(installationId, permissions, events) {
202
+ const db = getDb();
203
+ await db
204
+ .update(schema.githubInstallations)
205
+ .set({ permissions, events, updatedAt: new Date() })
206
+ .where(eq(schema.githubInstallations.installationId, installationId));
207
+ },
208
+ async suspend(installationId, suspendedBy) {
209
+ const db = getDb();
210
+ await db
211
+ .update(schema.githubInstallations)
212
+ .set({ suspended: true, suspendedAt: new Date(), suspendedBy, updatedAt: new Date() })
213
+ .where(eq(schema.githubInstallations.installationId, installationId));
214
+ },
215
+ async unsuspend(installationId) {
216
+ const db = getDb();
217
+ await db
218
+ .update(schema.githubInstallations)
219
+ .set({ suspended: false, suspendedAt: null, suspendedBy: null, updatedAt: new Date() })
220
+ .where(eq(schema.githubInstallations.installationId, installationId));
221
+ },
222
+ async delete(installationId) {
223
+ const db = getDb();
224
+ await db.delete(schema.githubInstallations).where(eq(schema.githubInstallations.installationId, installationId));
225
+ },
226
+ };
227
+ export const credentialQueries = {
228
+ async findByUserId(userId) {
229
+ const db = getDb();
230
+ return db.select().from(schema.credentials).where(eq(schema.credentials.userId, userId));
231
+ },
232
+ async findByUserAndWorkspace(userId, workspaceId) {
233
+ const db = getDb();
234
+ // Return credentials for this workspace OR legacy credentials (NULL workspace_id)
235
+ // This ensures backward compatibility with existing credentials
236
+ return db
237
+ .select()
238
+ .from(schema.credentials)
239
+ .where(and(eq(schema.credentials.userId, userId), or(eq(schema.credentials.workspaceId, workspaceId), isNull(schema.credentials.workspaceId))));
240
+ },
241
+ async findByUserWorkspaceAndProvider(userId, workspaceId, provider) {
242
+ const db = getDb();
243
+ const result = await db
244
+ .select()
245
+ .from(schema.credentials)
246
+ .where(and(eq(schema.credentials.userId, userId), eq(schema.credentials.workspaceId, workspaceId), eq(schema.credentials.provider, provider)));
247
+ return result[0] ?? null;
248
+ },
249
+ async findByUserAndProvider(userId, provider) {
250
+ // Legacy: returns first match (any workspace) - use findByUserWorkspaceAndProvider instead
251
+ const db = getDb();
252
+ const result = await db
253
+ .select()
254
+ .from(schema.credentials)
255
+ .where(and(eq(schema.credentials.userId, userId), eq(schema.credentials.provider, provider)));
256
+ return result[0] ?? null;
257
+ },
258
+ async upsert(data) {
259
+ const db = getDb();
260
+ const result = await db
261
+ .insert(schema.credentials)
262
+ .values(data)
263
+ .onConflictDoUpdate({
264
+ target: [schema.credentials.userId, schema.credentials.provider, schema.credentials.workspaceId],
265
+ set: {
266
+ scopes: data.scopes,
267
+ providerAccountId: data.providerAccountId,
268
+ providerAccountEmail: data.providerAccountEmail,
269
+ updatedAt: new Date(),
270
+ },
271
+ })
272
+ .returning();
273
+ return result[0];
274
+ },
275
+ async deleteForWorkspace(userId, workspaceId, provider) {
276
+ const db = getDb();
277
+ await db
278
+ .delete(schema.credentials)
279
+ .where(and(eq(schema.credentials.userId, userId), eq(schema.credentials.workspaceId, workspaceId), eq(schema.credentials.provider, provider)));
280
+ },
281
+ async delete(userId, provider) {
282
+ // Legacy: deletes all workspace credentials for this provider
283
+ const db = getDb();
284
+ await db
285
+ .delete(schema.credentials)
286
+ .where(and(eq(schema.credentials.userId, userId), eq(schema.credentials.provider, provider)));
287
+ },
288
+ };
289
+ export const workspaceQueries = {
290
+ async findById(id) {
291
+ const db = getDb();
292
+ const result = await db.select().from(schema.workspaces).where(eq(schema.workspaces.id, id));
293
+ return result[0] ?? null;
294
+ },
295
+ async findByUserId(userId) {
296
+ const db = getDb();
297
+ return db
298
+ .select()
299
+ .from(schema.workspaces)
300
+ .where(eq(schema.workspaces.userId, userId))
301
+ .orderBy(desc(schema.workspaces.createdAt));
302
+ },
303
+ async findByCustomDomain(domain) {
304
+ const db = getDb();
305
+ const result = await db
306
+ .select()
307
+ .from(schema.workspaces)
308
+ .where(eq(schema.workspaces.customDomain, domain));
309
+ return result[0] ?? null;
310
+ },
311
+ async findByRepoFullName(repoFullName) {
312
+ const db = getDb();
313
+ // Find repository by full name (case-insensitive), then get its workspace
314
+ const result = await db
315
+ .select({ workspace: schema.workspaces })
316
+ .from(schema.repositories)
317
+ .innerJoin(schema.workspaces, eq(schema.repositories.workspaceId, schema.workspaces.id))
318
+ .where(sql `LOWER(${schema.repositories.githubFullName}) = LOWER(${repoFullName})`)
319
+ .limit(1);
320
+ return result[0]?.workspace ?? null;
321
+ },
322
+ async findAll() {
323
+ const db = getDb();
324
+ return db
325
+ .select()
326
+ .from(schema.workspaces)
327
+ .orderBy(desc(schema.workspaces.createdAt));
328
+ },
329
+ async create(data) {
330
+ const db = getDb();
331
+ const result = await db.insert(schema.workspaces).values(data).returning();
332
+ return result[0];
333
+ },
334
+ async update(id, data) {
335
+ const db = getDb();
336
+ await db
337
+ .update(schema.workspaces)
338
+ .set({ ...data, updatedAt: new Date() })
339
+ .where(eq(schema.workspaces.id, id));
340
+ },
341
+ async updateStatus(id, status, options) {
342
+ const db = getDb();
343
+ await db
344
+ .update(schema.workspaces)
345
+ .set({
346
+ status,
347
+ computeId: options?.computeId,
348
+ publicUrl: options?.publicUrl,
349
+ errorMessage: options?.errorMessage,
350
+ updatedAt: new Date(),
351
+ })
352
+ .where(eq(schema.workspaces.id, id));
353
+ },
354
+ async updateConfig(id, config) {
355
+ const db = getDb();
356
+ await db
357
+ .update(schema.workspaces)
358
+ .set({
359
+ config,
360
+ updatedAt: new Date(),
361
+ })
362
+ .where(eq(schema.workspaces.id, id));
363
+ },
364
+ async setCustomDomain(id, customDomain, status = 'pending') {
365
+ const db = getDb();
366
+ await db
367
+ .update(schema.workspaces)
368
+ .set({ customDomain, customDomainStatus: status, updatedAt: new Date() })
369
+ .where(eq(schema.workspaces.id, id));
370
+ },
371
+ async updateCustomDomainStatus(id, status) {
372
+ const db = getDb();
373
+ await db
374
+ .update(schema.workspaces)
375
+ .set({ customDomainStatus: status, updatedAt: new Date() })
376
+ .where(eq(schema.workspaces.id, id));
377
+ },
378
+ async removeCustomDomain(id) {
379
+ const db = getDb();
380
+ await db
381
+ .update(schema.workspaces)
382
+ .set({ customDomain: null, customDomainStatus: null, updatedAt: new Date() })
383
+ .where(eq(schema.workspaces.id, id));
384
+ },
385
+ async delete(id) {
386
+ const db = getDb();
387
+ await db.delete(schema.workspaces).where(eq(schema.workspaces.id, id));
388
+ },
389
+ };
390
+ export const workspaceMemberQueries = {
391
+ async findByWorkspaceId(workspaceId) {
392
+ const db = getDb();
393
+ return db
394
+ .select()
395
+ .from(schema.workspaceMembers)
396
+ .where(eq(schema.workspaceMembers.workspaceId, workspaceId));
397
+ },
398
+ async findByUserId(userId) {
399
+ const db = getDb();
400
+ return db
401
+ .select()
402
+ .from(schema.workspaceMembers)
403
+ .where(and(eq(schema.workspaceMembers.userId, userId), isNotNull(schema.workspaceMembers.acceptedAt)));
404
+ },
405
+ async findMembership(workspaceId, userId) {
406
+ const db = getDb();
407
+ const result = await db
408
+ .select()
409
+ .from(schema.workspaceMembers)
410
+ .where(and(eq(schema.workspaceMembers.workspaceId, workspaceId), eq(schema.workspaceMembers.userId, userId)));
411
+ return result[0] ?? null;
412
+ },
413
+ async addMember(data) {
414
+ const db = getDb();
415
+ const result = await db
416
+ .insert(schema.workspaceMembers)
417
+ .values({
418
+ workspaceId: data.workspaceId,
419
+ userId: data.userId,
420
+ role: data.role,
421
+ invitedBy: data.invitedBy,
422
+ })
423
+ .returning();
424
+ return result[0];
425
+ },
426
+ async acceptInvite(workspaceId, userId) {
427
+ const db = getDb();
428
+ await db
429
+ .update(schema.workspaceMembers)
430
+ .set({ acceptedAt: new Date() })
431
+ .where(and(eq(schema.workspaceMembers.workspaceId, workspaceId), eq(schema.workspaceMembers.userId, userId)));
432
+ },
433
+ async updateRole(workspaceId, userId, role) {
434
+ const db = getDb();
435
+ await db
436
+ .update(schema.workspaceMembers)
437
+ .set({ role })
438
+ .where(and(eq(schema.workspaceMembers.workspaceId, workspaceId), eq(schema.workspaceMembers.userId, userId)));
439
+ },
440
+ async removeMember(workspaceId, userId) {
441
+ const db = getDb();
442
+ await db
443
+ .delete(schema.workspaceMembers)
444
+ .where(and(eq(schema.workspaceMembers.workspaceId, workspaceId), eq(schema.workspaceMembers.userId, userId)));
445
+ },
446
+ async getPendingInvites(userId) {
447
+ const db = getDb();
448
+ return db
449
+ .select()
450
+ .from(schema.workspaceMembers)
451
+ .where(and(eq(schema.workspaceMembers.userId, userId), isNull(schema.workspaceMembers.acceptedAt)));
452
+ },
453
+ async isOwner(workspaceId, userId) {
454
+ const db = getDb();
455
+ const result = await db
456
+ .select()
457
+ .from(schema.workspaceMembers)
458
+ .where(and(eq(schema.workspaceMembers.workspaceId, workspaceId), eq(schema.workspaceMembers.userId, userId), eq(schema.workspaceMembers.role, 'owner')));
459
+ return result.length > 0;
460
+ },
461
+ async canEdit(workspaceId, userId) {
462
+ const db = getDb();
463
+ const result = await db
464
+ .select()
465
+ .from(schema.workspaceMembers)
466
+ .where(and(eq(schema.workspaceMembers.workspaceId, workspaceId), eq(schema.workspaceMembers.userId, userId), isNotNull(schema.workspaceMembers.acceptedAt)));
467
+ const member = result[0];
468
+ return !!member && ['owner', 'admin', 'member'].includes(member.role);
469
+ },
470
+ async canView(workspaceId, userId) {
471
+ const db = getDb();
472
+ const result = await db
473
+ .select()
474
+ .from(schema.workspaceMembers)
475
+ .where(and(eq(schema.workspaceMembers.workspaceId, workspaceId), eq(schema.workspaceMembers.userId, userId), isNotNull(schema.workspaceMembers.acceptedAt)));
476
+ return result.length > 0;
477
+ },
478
+ };
479
+ export const linkedDaemonQueries = {
480
+ async findById(id) {
481
+ const db = getDb();
482
+ const result = await db.select().from(schema.linkedDaemons).where(eq(schema.linkedDaemons.id, id));
483
+ return result[0] ?? null;
484
+ },
485
+ async findByUserId(userId) {
486
+ const db = getDb();
487
+ return db
488
+ .select()
489
+ .from(schema.linkedDaemons)
490
+ .where(eq(schema.linkedDaemons.userId, userId))
491
+ .orderBy(desc(schema.linkedDaemons.lastSeenAt));
492
+ },
493
+ async findByWorkspaceId(workspaceId) {
494
+ const db = getDb();
495
+ return db
496
+ .select()
497
+ .from(schema.linkedDaemons)
498
+ .where(eq(schema.linkedDaemons.workspaceId, workspaceId))
499
+ .orderBy(desc(schema.linkedDaemons.lastSeenAt));
500
+ },
501
+ async findByMachineId(userId, machineId) {
502
+ const db = getDb();
503
+ const result = await db
504
+ .select()
505
+ .from(schema.linkedDaemons)
506
+ .where(and(eq(schema.linkedDaemons.userId, userId), eq(schema.linkedDaemons.machineId, machineId)));
507
+ return result[0] ?? null;
508
+ },
509
+ async findByApiKeyHash(apiKeyHash) {
510
+ const db = getDb();
511
+ const result = await db
512
+ .select()
513
+ .from(schema.linkedDaemons)
514
+ .where(eq(schema.linkedDaemons.apiKeyHash, apiKeyHash));
515
+ return result[0] ?? null;
516
+ },
517
+ async create(data) {
518
+ const db = getDb();
519
+ const result = await db
520
+ .insert(schema.linkedDaemons)
521
+ .values({ ...data, lastSeenAt: new Date() })
522
+ .returning();
523
+ return result[0];
524
+ },
525
+ async update(id, data) {
526
+ const db = getDb();
527
+ await db
528
+ .update(schema.linkedDaemons)
529
+ .set({ ...data, updatedAt: new Date() })
530
+ .where(eq(schema.linkedDaemons.id, id));
531
+ },
532
+ async updateLastSeen(id) {
533
+ const db = getDb();
534
+ await db
535
+ .update(schema.linkedDaemons)
536
+ .set({ lastSeenAt: new Date(), status: 'online', updatedAt: new Date() })
537
+ .where(eq(schema.linkedDaemons.id, id));
538
+ },
539
+ async delete(id) {
540
+ const db = getDb();
541
+ await db.delete(schema.linkedDaemons).where(eq(schema.linkedDaemons.id, id));
542
+ },
543
+ async markStale() {
544
+ const db = getDb();
545
+ const twoMinutesAgo = new Date(Date.now() - 2 * 60 * 1000);
546
+ const result = await db
547
+ .update(schema.linkedDaemons)
548
+ .set({ status: 'offline' })
549
+ .where(and(eq(schema.linkedDaemons.status, 'online'), lt(schema.linkedDaemons.lastSeenAt, twoMinutesAgo)));
550
+ return result.rowCount ?? 0;
551
+ },
552
+ async getAllAgentsForUser(userId) {
553
+ const db = getDb();
554
+ const daemons = await db
555
+ .select()
556
+ .from(schema.linkedDaemons)
557
+ .where(eq(schema.linkedDaemons.userId, userId));
558
+ return daemons.map((d) => ({
559
+ daemonId: d.id,
560
+ daemonName: d.name,
561
+ machineId: d.machineId,
562
+ agents: d.metadata?.agents || [],
563
+ }));
564
+ },
565
+ async getPendingUpdates(id) {
566
+ const db = getDb();
567
+ const result = await db.select().from(schema.linkedDaemons).where(eq(schema.linkedDaemons.id, id));
568
+ const daemon = result[0];
569
+ if (!daemon)
570
+ return [];
571
+ const updates = daemon.pendingUpdates || [];
572
+ // Clear after reading
573
+ if (updates.length > 0) {
574
+ await db
575
+ .update(schema.linkedDaemons)
576
+ .set({ pendingUpdates: [] })
577
+ .where(eq(schema.linkedDaemons.id, id));
578
+ }
579
+ return updates;
580
+ },
581
+ async queueUpdate(id, update) {
582
+ const db = getDb();
583
+ const result = await db.select().from(schema.linkedDaemons).where(eq(schema.linkedDaemons.id, id));
584
+ const daemon = result[0];
585
+ if (!daemon)
586
+ return;
587
+ const existing = daemon.pendingUpdates || [];
588
+ await db
589
+ .update(schema.linkedDaemons)
590
+ .set({ pendingUpdates: [...existing, update], updatedAt: new Date() })
591
+ .where(eq(schema.linkedDaemons.id, id));
592
+ },
593
+ async queueMessage(id, message) {
594
+ const db = getDb();
595
+ const result = await db.select().from(schema.linkedDaemons).where(eq(schema.linkedDaemons.id, id));
596
+ const daemon = result[0];
597
+ if (!daemon)
598
+ return;
599
+ const existing = daemon.messageQueue || [];
600
+ await db
601
+ .update(schema.linkedDaemons)
602
+ .set({ messageQueue: [...existing, message], updatedAt: new Date() })
603
+ .where(eq(schema.linkedDaemons.id, id));
604
+ },
605
+ async getQueuedMessages(id) {
606
+ const db = getDb();
607
+ const result = await db.select().from(schema.linkedDaemons).where(eq(schema.linkedDaemons.id, id));
608
+ const daemon = result[0];
609
+ return daemon?.messageQueue || [];
610
+ },
611
+ async clearMessageQueue(id) {
612
+ const db = getDb();
613
+ await db
614
+ .update(schema.linkedDaemons)
615
+ .set({ messageQueue: [] })
616
+ .where(eq(schema.linkedDaemons.id, id));
617
+ },
618
+ };
619
+ export const projectGroupQueries = {
620
+ async findById(id) {
621
+ const db = getDb();
622
+ const result = await db.select().from(schema.projectGroups).where(eq(schema.projectGroups.id, id));
623
+ return result[0] ?? null;
624
+ },
625
+ async findByUserId(userId) {
626
+ const db = getDb();
627
+ return db
628
+ .select()
629
+ .from(schema.projectGroups)
630
+ .where(eq(schema.projectGroups.userId, userId))
631
+ .orderBy(schema.projectGroups.sortOrder, schema.projectGroups.name);
632
+ },
633
+ async findByName(userId, name) {
634
+ const db = getDb();
635
+ const result = await db
636
+ .select()
637
+ .from(schema.projectGroups)
638
+ .where(and(eq(schema.projectGroups.userId, userId), eq(schema.projectGroups.name, name)));
639
+ return result[0] ?? null;
640
+ },
641
+ async create(data) {
642
+ const db = getDb();
643
+ const result = await db.insert(schema.projectGroups).values(data).returning();
644
+ return result[0];
645
+ },
646
+ async update(id, data) {
647
+ const db = getDb();
648
+ await db
649
+ .update(schema.projectGroups)
650
+ .set({ ...data, updatedAt: new Date() })
651
+ .where(eq(schema.projectGroups.id, id));
652
+ },
653
+ async delete(id) {
654
+ const db = getDb();
655
+ // Repositories in this group will have projectGroupId set to null (ON DELETE SET NULL)
656
+ await db.delete(schema.projectGroups).where(eq(schema.projectGroups.id, id));
657
+ },
658
+ async findWithRepositories(id) {
659
+ const db = getDb();
660
+ const group = await db.select().from(schema.projectGroups).where(eq(schema.projectGroups.id, id));
661
+ if (!group[0])
662
+ return null;
663
+ const repos = await db
664
+ .select()
665
+ .from(schema.repositories)
666
+ .where(eq(schema.repositories.projectGroupId, id))
667
+ .orderBy(schema.repositories.githubFullName);
668
+ return { ...group[0], repositories: repos };
669
+ },
670
+ async findAllWithRepositories(userId) {
671
+ const db = getDb();
672
+ const groups = await db
673
+ .select()
674
+ .from(schema.projectGroups)
675
+ .where(eq(schema.projectGroups.userId, userId))
676
+ .orderBy(schema.projectGroups.sortOrder, schema.projectGroups.name);
677
+ // Get repositories for each group
678
+ const result = await Promise.all(groups.map(async (group) => {
679
+ const repos = await db
680
+ .select()
681
+ .from(schema.repositories)
682
+ .where(eq(schema.repositories.projectGroupId, group.id))
683
+ .orderBy(schema.repositories.githubFullName);
684
+ return { ...group, repositories: repos };
685
+ }));
686
+ // Also get ungrouped repositories
687
+ const ungroupedRepos = await db
688
+ .select()
689
+ .from(schema.repositories)
690
+ .where(and(eq(schema.repositories.userId, userId), isNull(schema.repositories.projectGroupId)))
691
+ .orderBy(schema.repositories.githubFullName);
692
+ return { groups: result, ungroupedRepositories: ungroupedRepos };
693
+ },
694
+ async updateCoordinatorAgent(id, config) {
695
+ const db = getDb();
696
+ await db
697
+ .update(schema.projectGroups)
698
+ .set({ coordinatorAgent: config, updatedAt: new Date() })
699
+ .where(eq(schema.projectGroups.id, id));
700
+ },
701
+ async reorder(userId, orderedIds) {
702
+ const db = getDb();
703
+ // Update sort_order for each group
704
+ await Promise.all(orderedIds.map((id, index) => db
705
+ .update(schema.projectGroups)
706
+ .set({ sortOrder: index, updatedAt: new Date() })
707
+ .where(and(eq(schema.projectGroups.id, id), eq(schema.projectGroups.userId, userId)))));
708
+ },
709
+ };
710
+ export const repositoryQueries = {
711
+ async findById(id) {
712
+ const db = getDb();
713
+ const result = await db.select().from(schema.repositories).where(eq(schema.repositories.id, id));
714
+ return result[0] ?? null;
715
+ },
716
+ async findByFullName(fullName) {
717
+ const db = getDb();
718
+ const result = await db
719
+ .select()
720
+ .from(schema.repositories)
721
+ .where(eq(schema.repositories.githubFullName, fullName));
722
+ return result[0] ?? null;
723
+ },
724
+ async findByGithubFullName(fullName) {
725
+ const db = getDb();
726
+ // Use case-insensitive match since GitHub repo names are case-insensitive
727
+ return db
728
+ .select()
729
+ .from(schema.repositories)
730
+ .where(sql `LOWER(${schema.repositories.githubFullName}) = LOWER(${fullName})`);
731
+ },
732
+ async findByUserId(userId) {
733
+ const db = getDb();
734
+ return db
735
+ .select()
736
+ .from(schema.repositories)
737
+ .where(eq(schema.repositories.userId, userId))
738
+ .orderBy(schema.repositories.githubFullName);
739
+ },
740
+ async findByWorkspaceId(workspaceId) {
741
+ const db = getDb();
742
+ return db
743
+ .select()
744
+ .from(schema.repositories)
745
+ .where(eq(schema.repositories.workspaceId, workspaceId));
746
+ },
747
+ async findByProjectGroupId(projectGroupId) {
748
+ const db = getDb();
749
+ return db
750
+ .select()
751
+ .from(schema.repositories)
752
+ .where(eq(schema.repositories.projectGroupId, projectGroupId))
753
+ .orderBy(schema.repositories.githubFullName);
754
+ },
755
+ async upsert(data) {
756
+ const db = getDb();
757
+ const result = await db
758
+ .insert(schema.repositories)
759
+ .values(data)
760
+ .onConflictDoUpdate({
761
+ target: [schema.repositories.userId, schema.repositories.githubFullName],
762
+ set: {
763
+ githubId: data.githubId,
764
+ defaultBranch: data.defaultBranch,
765
+ isPrivate: data.isPrivate,
766
+ syncStatus: data.syncStatus,
767
+ nangoConnectionId: data.nangoConnectionId,
768
+ installationId: data.installationId,
769
+ lastSyncedAt: data.lastSyncedAt,
770
+ updatedAt: new Date(),
771
+ },
772
+ })
773
+ .returning();
774
+ return result[0];
775
+ },
776
+ async assignToWorkspace(repoId, workspaceId) {
777
+ const db = getDb();
778
+ await db
779
+ .update(schema.repositories)
780
+ .set({ workspaceId, updatedAt: new Date() })
781
+ .where(eq(schema.repositories.id, repoId));
782
+ },
783
+ async assignToGroup(repoId, projectGroupId) {
784
+ const db = getDb();
785
+ await db
786
+ .update(schema.repositories)
787
+ .set({ projectGroupId, updatedAt: new Date() })
788
+ .where(eq(schema.repositories.id, repoId));
789
+ },
790
+ async updateProjectAgent(id, config) {
791
+ const db = getDb();
792
+ await db
793
+ .update(schema.repositories)
794
+ .set({ projectAgent: config, updatedAt: new Date() })
795
+ .where(eq(schema.repositories.id, id));
796
+ },
797
+ async updateSyncStatus(id, status, lastSyncedAt) {
798
+ const db = getDb();
799
+ const updates = { syncStatus: status, updatedAt: new Date() };
800
+ if (lastSyncedAt) {
801
+ updates.lastSyncedAt = lastSyncedAt;
802
+ }
803
+ await db
804
+ .update(schema.repositories)
805
+ .set(updates)
806
+ .where(eq(schema.repositories.id, id));
807
+ },
808
+ async delete(id) {
809
+ const db = getDb();
810
+ await db.delete(schema.repositories).where(eq(schema.repositories.id, id));
811
+ },
812
+ };
813
+ export const agentSessionQueries = {
814
+ async findById(id) {
815
+ const db = getDb();
816
+ const result = await db.select().from(schema.agentSessions).where(eq(schema.agentSessions.id, id));
817
+ return result[0] ?? null;
818
+ },
819
+ async findByWorkspaceId(workspaceId) {
820
+ const db = getDb();
821
+ return db
822
+ .select()
823
+ .from(schema.agentSessions)
824
+ .where(eq(schema.agentSessions.workspaceId, workspaceId))
825
+ .orderBy(desc(schema.agentSessions.startedAt));
826
+ },
827
+ async findActiveByWorkspace(workspaceId) {
828
+ const db = getDb();
829
+ return db
830
+ .select()
831
+ .from(schema.agentSessions)
832
+ .where(and(eq(schema.agentSessions.workspaceId, workspaceId), eq(schema.agentSessions.status, 'active')));
833
+ },
834
+ async create(data) {
835
+ const db = getDb();
836
+ const result = await db.insert(schema.agentSessions).values(data).returning();
837
+ return result[0];
838
+ },
839
+ async endSession(id, endMarker) {
840
+ const db = getDb();
841
+ await db
842
+ .update(schema.agentSessions)
843
+ .set({
844
+ status: 'ended',
845
+ endedAt: new Date(),
846
+ endMarker: endMarker ?? null,
847
+ })
848
+ .where(eq(schema.agentSessions.id, id));
849
+ },
850
+ async delete(id) {
851
+ const db = getDb();
852
+ await db.delete(schema.agentSessions).where(eq(schema.agentSessions.id, id));
853
+ },
854
+ };
855
+ export const agentSummaryQueries = {
856
+ async findBySessionId(sessionId) {
857
+ const db = getDb();
858
+ return db
859
+ .select()
860
+ .from(schema.agentSummaries)
861
+ .where(eq(schema.agentSummaries.sessionId, sessionId))
862
+ .orderBy(schema.agentSummaries.createdAt);
863
+ },
864
+ async findLatestByAgent(agentName) {
865
+ const db = getDb();
866
+ const result = await db
867
+ .select()
868
+ .from(schema.agentSummaries)
869
+ .where(eq(schema.agentSummaries.agentName, agentName))
870
+ .orderBy(desc(schema.agentSummaries.createdAt))
871
+ .limit(1);
872
+ return result[0] ?? null;
873
+ },
874
+ async create(data) {
875
+ const db = getDb();
876
+ const result = await db.insert(schema.agentSummaries).values(data).returning();
877
+ return result[0];
878
+ },
879
+ async deleteBySession(sessionId) {
880
+ const db = getDb();
881
+ await db.delete(schema.agentSummaries).where(eq(schema.agentSummaries.sessionId, sessionId));
882
+ },
883
+ };
884
+ export const ciFailureEventQueries = {
885
+ async findById(id) {
886
+ const db = getDb();
887
+ const result = await db.select().from(schema.ciFailureEvents).where(eq(schema.ciFailureEvents.id, id));
888
+ return result[0] ?? null;
889
+ },
890
+ async findByRepository(repository, limit = 50) {
891
+ const db = getDb();
892
+ return db
893
+ .select()
894
+ .from(schema.ciFailureEvents)
895
+ .where(eq(schema.ciFailureEvents.repository, repository))
896
+ .orderBy(desc(schema.ciFailureEvents.createdAt))
897
+ .limit(limit);
898
+ },
899
+ async findByPR(repository, prNumber) {
900
+ const db = getDb();
901
+ return db
902
+ .select()
903
+ .from(schema.ciFailureEvents)
904
+ .where(and(eq(schema.ciFailureEvents.repository, repository), eq(schema.ciFailureEvents.prNumber, prNumber)))
905
+ .orderBy(desc(schema.ciFailureEvents.createdAt));
906
+ },
907
+ async findRecentUnprocessed(limit = 100) {
908
+ const db = getDb();
909
+ return db
910
+ .select()
911
+ .from(schema.ciFailureEvents)
912
+ .where(isNull(schema.ciFailureEvents.processedAt))
913
+ .orderBy(schema.ciFailureEvents.createdAt)
914
+ .limit(limit);
915
+ },
916
+ async create(data) {
917
+ const db = getDb();
918
+ const result = await db.insert(schema.ciFailureEvents).values(data).returning();
919
+ return result[0];
920
+ },
921
+ async markProcessed(id, agentSpawned) {
922
+ const db = getDb();
923
+ await db
924
+ .update(schema.ciFailureEvents)
925
+ .set({ processedAt: new Date(), agentSpawned })
926
+ .where(eq(schema.ciFailureEvents.id, id));
927
+ },
928
+ async delete(id) {
929
+ const db = getDb();
930
+ await db.delete(schema.ciFailureEvents).where(eq(schema.ciFailureEvents.id, id));
931
+ },
932
+ };
933
+ export const ciFixAttemptQueries = {
934
+ async findById(id) {
935
+ const db = getDb();
936
+ const result = await db.select().from(schema.ciFixAttempts).where(eq(schema.ciFixAttempts.id, id));
937
+ return result[0] ?? null;
938
+ },
939
+ async findByFailureEvent(failureEventId) {
940
+ const db = getDb();
941
+ return db
942
+ .select()
943
+ .from(schema.ciFixAttempts)
944
+ .where(eq(schema.ciFixAttempts.failureEventId, failureEventId))
945
+ .orderBy(desc(schema.ciFixAttempts.startedAt));
946
+ },
947
+ async findActiveByRepository(repository) {
948
+ const db = getDb();
949
+ // Find active fix attempts by joining with failure events
950
+ return db
951
+ .select({
952
+ id: schema.ciFixAttempts.id,
953
+ failureEventId: schema.ciFixAttempts.failureEventId,
954
+ agentId: schema.ciFixAttempts.agentId,
955
+ agentName: schema.ciFixAttempts.agentName,
956
+ status: schema.ciFixAttempts.status,
957
+ commitSha: schema.ciFixAttempts.commitSha,
958
+ errorMessage: schema.ciFixAttempts.errorMessage,
959
+ startedAt: schema.ciFixAttempts.startedAt,
960
+ completedAt: schema.ciFixAttempts.completedAt,
961
+ })
962
+ .from(schema.ciFixAttempts)
963
+ .innerJoin(schema.ciFailureEvents, eq(schema.ciFixAttempts.failureEventId, schema.ciFailureEvents.id))
964
+ .where(and(eq(schema.ciFailureEvents.repository, repository), sql `${schema.ciFixAttempts.status} IN ('pending', 'in_progress')`));
965
+ },
966
+ async create(data) {
967
+ const db = getDb();
968
+ const result = await db.insert(schema.ciFixAttempts).values(data).returning();
969
+ return result[0];
970
+ },
971
+ async updateStatus(id, status, errorMessage) {
972
+ const db = getDb();
973
+ const updates = { status };
974
+ if (errorMessage) {
975
+ updates.errorMessage = errorMessage;
976
+ }
977
+ await db
978
+ .update(schema.ciFixAttempts)
979
+ .set(updates)
980
+ .where(eq(schema.ciFixAttempts.id, id));
981
+ },
982
+ async complete(id, status, commitSha, errorMessage) {
983
+ const db = getDb();
984
+ await db
985
+ .update(schema.ciFixAttempts)
986
+ .set({
987
+ status,
988
+ completedAt: new Date(),
989
+ commitSha: commitSha ?? null,
990
+ errorMessage: errorMessage ?? null,
991
+ })
992
+ .where(eq(schema.ciFixAttempts.id, id));
993
+ },
994
+ };
995
+ export const issueAssignmentQueries = {
996
+ async findById(id) {
997
+ const db = getDb();
998
+ const result = await db.select().from(schema.issueAssignments).where(eq(schema.issueAssignments.id, id));
999
+ return result[0] ?? null;
1000
+ },
1001
+ async findByRepository(repository, limit = 50) {
1002
+ const db = getDb();
1003
+ return db
1004
+ .select()
1005
+ .from(schema.issueAssignments)
1006
+ .where(eq(schema.issueAssignments.repository, repository))
1007
+ .orderBy(desc(schema.issueAssignments.createdAt))
1008
+ .limit(limit);
1009
+ },
1010
+ async findByIssue(repository, issueNumber) {
1011
+ const db = getDb();
1012
+ const result = await db
1013
+ .select()
1014
+ .from(schema.issueAssignments)
1015
+ .where(and(eq(schema.issueAssignments.repository, repository), eq(schema.issueAssignments.issueNumber, issueNumber)));
1016
+ return result[0] ?? null;
1017
+ },
1018
+ async findByAgent(agentId) {
1019
+ const db = getDb();
1020
+ return db
1021
+ .select()
1022
+ .from(schema.issueAssignments)
1023
+ .where(eq(schema.issueAssignments.agentId, agentId))
1024
+ .orderBy(desc(schema.issueAssignments.createdAt));
1025
+ },
1026
+ async findPending(limit = 100) {
1027
+ const db = getDb();
1028
+ return db
1029
+ .select()
1030
+ .from(schema.issueAssignments)
1031
+ .where(eq(schema.issueAssignments.status, 'pending'))
1032
+ .orderBy(schema.issueAssignments.createdAt)
1033
+ .limit(limit);
1034
+ },
1035
+ async create(data) {
1036
+ const db = getDb();
1037
+ const result = await db.insert(schema.issueAssignments).values(data).returning();
1038
+ return result[0];
1039
+ },
1040
+ async assignAgent(id, agentId, agentName) {
1041
+ const db = getDb();
1042
+ await db
1043
+ .update(schema.issueAssignments)
1044
+ .set({
1045
+ agentId,
1046
+ agentName,
1047
+ assignedAt: new Date(),
1048
+ status: 'assigned',
1049
+ updatedAt: new Date(),
1050
+ })
1051
+ .where(eq(schema.issueAssignments.id, id));
1052
+ },
1053
+ async updateStatus(id, status, resolution) {
1054
+ const db = getDb();
1055
+ const updates = { status, updatedAt: new Date() };
1056
+ if (resolution) {
1057
+ updates.resolution = resolution;
1058
+ }
1059
+ await db
1060
+ .update(schema.issueAssignments)
1061
+ .set(updates)
1062
+ .where(eq(schema.issueAssignments.id, id));
1063
+ },
1064
+ async linkPR(id, prNumber) {
1065
+ const db = getDb();
1066
+ await db
1067
+ .update(schema.issueAssignments)
1068
+ .set({ linkedPrNumber: prNumber, updatedAt: new Date() })
1069
+ .where(eq(schema.issueAssignments.id, id));
1070
+ },
1071
+ };
1072
+ export const commentMentionQueries = {
1073
+ async findById(id) {
1074
+ const db = getDb();
1075
+ const result = await db.select().from(schema.commentMentions).where(eq(schema.commentMentions.id, id));
1076
+ return result[0] ?? null;
1077
+ },
1078
+ async findByRepository(repository, limit = 50) {
1079
+ const db = getDb();
1080
+ return db
1081
+ .select()
1082
+ .from(schema.commentMentions)
1083
+ .where(eq(schema.commentMentions.repository, repository))
1084
+ .orderBy(desc(schema.commentMentions.createdAt))
1085
+ .limit(limit);
1086
+ },
1087
+ async findBySource(sourceType, sourceId) {
1088
+ const db = getDb();
1089
+ const result = await db
1090
+ .select()
1091
+ .from(schema.commentMentions)
1092
+ .where(and(eq(schema.commentMentions.sourceType, sourceType), eq(schema.commentMentions.sourceId, sourceId)));
1093
+ return result[0] ?? null;
1094
+ },
1095
+ async findPending(limit = 100) {
1096
+ const db = getDb();
1097
+ return db
1098
+ .select()
1099
+ .from(schema.commentMentions)
1100
+ .where(eq(schema.commentMentions.status, 'pending'))
1101
+ .orderBy(schema.commentMentions.createdAt)
1102
+ .limit(limit);
1103
+ },
1104
+ async findByMentionedAgent(mentionedAgent, limit = 50) {
1105
+ const db = getDb();
1106
+ return db
1107
+ .select()
1108
+ .from(schema.commentMentions)
1109
+ .where(eq(schema.commentMentions.mentionedAgent, mentionedAgent))
1110
+ .orderBy(desc(schema.commentMentions.createdAt))
1111
+ .limit(limit);
1112
+ },
1113
+ async create(data) {
1114
+ const db = getDb();
1115
+ const result = await db.insert(schema.commentMentions).values(data).returning();
1116
+ return result[0];
1117
+ },
1118
+ async markProcessing(id, agentId, agentName) {
1119
+ const db = getDb();
1120
+ await db
1121
+ .update(schema.commentMentions)
1122
+ .set({ status: 'processing', agentId, agentName })
1123
+ .where(eq(schema.commentMentions.id, id));
1124
+ },
1125
+ async markResponded(id, responseCommentId, responseBody) {
1126
+ const db = getDb();
1127
+ await db
1128
+ .update(schema.commentMentions)
1129
+ .set({
1130
+ status: 'responded',
1131
+ responseCommentId,
1132
+ responseBody,
1133
+ respondedAt: new Date(),
1134
+ })
1135
+ .where(eq(schema.commentMentions.id, id));
1136
+ },
1137
+ async markIgnored(id) {
1138
+ const db = getDb();
1139
+ await db
1140
+ .update(schema.commentMentions)
1141
+ .set({ status: 'ignored' })
1142
+ .where(eq(schema.commentMentions.id, id));
1143
+ },
1144
+ };
1145
+ export const channelQueries = {
1146
+ async findById(id) {
1147
+ const db = getDb();
1148
+ const result = await db.select().from(schema.channels).where(eq(schema.channels.id, id));
1149
+ return result[0] ?? null;
1150
+ },
1151
+ async findByWorkspaceId(workspaceId) {
1152
+ const db = getDb();
1153
+ return db
1154
+ .select()
1155
+ .from(schema.channels)
1156
+ .where(eq(schema.channels.workspaceId, workspaceId))
1157
+ .orderBy(desc(schema.channels.lastActivityAt));
1158
+ },
1159
+ async findByWorkspaceAndChannelId(workspaceId, channelId) {
1160
+ const db = getDb();
1161
+ const result = await db
1162
+ .select()
1163
+ .from(schema.channels)
1164
+ .where(and(eq(schema.channels.workspaceId, workspaceId), eq(schema.channels.channelId, channelId)));
1165
+ return result[0] ?? null;
1166
+ },
1167
+ async create(channel) {
1168
+ const db = getDb();
1169
+ const result = await db.insert(schema.channels).values(channel).returning();
1170
+ return result[0];
1171
+ },
1172
+ async update(id, updates) {
1173
+ const db = getDb();
1174
+ await db
1175
+ .update(schema.channels)
1176
+ .set({ ...updates, updatedAt: new Date() })
1177
+ .where(eq(schema.channels.id, id));
1178
+ },
1179
+ async archive(id) {
1180
+ const db = getDb();
1181
+ await db
1182
+ .update(schema.channels)
1183
+ .set({ status: 'archived', updatedAt: new Date() })
1184
+ .where(eq(schema.channels.id, id));
1185
+ },
1186
+ async unarchive(id) {
1187
+ const db = getDb();
1188
+ await db
1189
+ .update(schema.channels)
1190
+ .set({ status: 'active', updatedAt: new Date() })
1191
+ .where(eq(schema.channels.id, id));
1192
+ },
1193
+ async delete(id) {
1194
+ const db = getDb();
1195
+ await db.delete(schema.channels).where(eq(schema.channels.id, id));
1196
+ },
1197
+ };
1198
+ export const channelMemberQueries = {
1199
+ async findByChannelId(channelId) {
1200
+ const db = getDb();
1201
+ return db
1202
+ .select()
1203
+ .from(schema.channelMembers)
1204
+ .where(eq(schema.channelMembers.channelId, channelId));
1205
+ },
1206
+ async findByMemberId(memberId) {
1207
+ const db = getDb();
1208
+ return db
1209
+ .select()
1210
+ .from(schema.channelMembers)
1211
+ .where(eq(schema.channelMembers.memberId, memberId));
1212
+ },
1213
+ async findMembership(channelId, memberId) {
1214
+ const db = getDb();
1215
+ const result = await db
1216
+ .select()
1217
+ .from(schema.channelMembers)
1218
+ .where(and(eq(schema.channelMembers.channelId, channelId), eq(schema.channelMembers.memberId, memberId)));
1219
+ return result[0] ?? null;
1220
+ },
1221
+ async addMember(member) {
1222
+ const db = getDb();
1223
+ const result = await db.insert(schema.channelMembers).values(member).returning();
1224
+ return result[0];
1225
+ },
1226
+ async removeMember(channelId, memberId) {
1227
+ const db = getDb();
1228
+ await db
1229
+ .delete(schema.channelMembers)
1230
+ .where(and(eq(schema.channelMembers.channelId, channelId), eq(schema.channelMembers.memberId, memberId)));
1231
+ },
1232
+ async updateRole(channelId, memberId, role) {
1233
+ const db = getDb();
1234
+ await db
1235
+ .update(schema.channelMembers)
1236
+ .set({ role })
1237
+ .where(and(eq(schema.channelMembers.channelId, channelId), eq(schema.channelMembers.memberId, memberId)));
1238
+ },
1239
+ async countByChannelIds(channelIds) {
1240
+ if (channelIds.length === 0) {
1241
+ return new Map();
1242
+ }
1243
+ const db = getDb();
1244
+ const results = await db
1245
+ .select({
1246
+ channelId: schema.channelMembers.channelId,
1247
+ count: sql `count(*)::int`,
1248
+ })
1249
+ .from(schema.channelMembers)
1250
+ .where(inArray(schema.channelMembers.channelId, channelIds))
1251
+ .groupBy(schema.channelMembers.channelId);
1252
+ const countMap = new Map();
1253
+ for (const row of results) {
1254
+ countMap.set(row.channelId, row.count);
1255
+ }
1256
+ return countMap;
1257
+ },
1258
+ };
1259
+ // ============================================================================
1260
+ // Migration helper
1261
+ // ============================================================================
1262
+ export async function runMigrations() {
1263
+ const { migrate } = await import('drizzle-orm/node-postgres/migrator');
1264
+ const { fileURLToPath } = await import('node:url');
1265
+ const { dirname, join } = await import('node:path');
1266
+ // Get migrations folder relative to this file (works in both dev and Docker)
1267
+ const __filename = fileURLToPath(import.meta.url);
1268
+ const __dirname = dirname(__filename);
1269
+ // In dist: packages/cloud/dist/db/drizzle.js -> need to go to packages/cloud/src/db/migrations
1270
+ // The migrations are in src/db/migrations, not dist/db/migrations
1271
+ const migrationsFolder = join(__dirname, '..', '..', 'src', 'db', 'migrations');
1272
+ const db = getDb();
1273
+ await migrate(db, { migrationsFolder });
1274
+ console.log('Migrations complete');
1275
+ }
1276
+ // ============================================================================
1277
+ // Close connections
1278
+ // ============================================================================
1279
+ export async function closeDb() {
1280
+ if (pool) {
1281
+ await pool.end();
1282
+ pool = null;
1283
+ drizzleDb = null;
1284
+ }
1285
+ }
1286
+ //# sourceMappingURL=drizzle.js.map