@intelligo-dev/core 1.0.0-beta.1

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 (254) hide show
  1. package/LICENSE +201 -0
  2. package/dist/conversations/errors.d.ts +31 -0
  3. package/dist/conversations/errors.d.ts.map +1 -0
  4. package/dist/conversations/errors.js +25 -0
  5. package/dist/conversations/errors.js.map +1 -0
  6. package/dist/conversations/index.d.ts +13 -0
  7. package/dist/conversations/index.d.ts.map +1 -0
  8. package/dist/conversations/index.js +12 -0
  9. package/dist/conversations/index.js.map +1 -0
  10. package/dist/conversations/service.d.ts +132 -0
  11. package/dist/conversations/service.d.ts.map +1 -0
  12. package/dist/conversations/service.js +347 -0
  13. package/dist/conversations/service.js.map +1 -0
  14. package/dist/conversations/types.d.ts +10 -0
  15. package/dist/conversations/types.d.ts.map +1 -0
  16. package/dist/conversations/types.js +5 -0
  17. package/dist/conversations/types.js.map +1 -0
  18. package/dist/db/client.d.ts +19 -0
  19. package/dist/db/client.d.ts.map +1 -0
  20. package/dist/db/client.js +52 -0
  21. package/dist/db/client.js.map +1 -0
  22. package/dist/db/index.d.ts +4 -0
  23. package/dist/db/index.d.ts.map +1 -0
  24. package/dist/db/index.js +8 -0
  25. package/dist/db/index.js.map +1 -0
  26. package/dist/db/schema/agents.d.ts +400 -0
  27. package/dist/db/schema/agents.d.ts.map +1 -0
  28. package/dist/db/schema/agents.js +39 -0
  29. package/dist/db/schema/agents.js.map +1 -0
  30. package/dist/db/schema/ai.d.ts +2222 -0
  31. package/dist/db/schema/ai.d.ts.map +1 -0
  32. package/dist/db/schema/ai.js +313 -0
  33. package/dist/db/schema/ai.js.map +1 -0
  34. package/dist/db/schema/auth.d.ts +1222 -0
  35. package/dist/db/schema/auth.d.ts.map +1 -0
  36. package/dist/db/schema/auth.js +162 -0
  37. package/dist/db/schema/auth.js.map +1 -0
  38. package/dist/db/schema/billing.d.ts +1199 -0
  39. package/dist/db/schema/billing.d.ts.map +1 -0
  40. package/dist/db/schema/billing.js +150 -0
  41. package/dist/db/schema/billing.js.map +1 -0
  42. package/dist/db/schema/feature-flags.d.ts +143 -0
  43. package/dist/db/schema/feature-flags.d.ts.map +1 -0
  44. package/dist/db/schema/feature-flags.js +25 -0
  45. package/dist/db/schema/feature-flags.js.map +1 -0
  46. package/dist/db/schema/identity.d.ts +1295 -0
  47. package/dist/db/schema/identity.d.ts.map +1 -0
  48. package/dist/db/schema/identity.js +258 -0
  49. package/dist/db/schema/identity.js.map +1 -0
  50. package/dist/db/schema/index.d.ts +16 -0
  51. package/dist/db/schema/index.d.ts.map +1 -0
  52. package/dist/db/schema/index.js +43 -0
  53. package/dist/db/schema/index.js.map +1 -0
  54. package/dist/db/schema/notifications.d.ts +182 -0
  55. package/dist/db/schema/notifications.d.ts.map +1 -0
  56. package/dist/db/schema/notifications.js +38 -0
  57. package/dist/db/schema/notifications.js.map +1 -0
  58. package/dist/db/schema/rag.d.ts +171 -0
  59. package/dist/db/schema/rag.d.ts.map +1 -0
  60. package/dist/db/schema/rag.js +42 -0
  61. package/dist/db/schema/rag.js.map +1 -0
  62. package/dist/db/schema/usage.d.ts +1193 -0
  63. package/dist/db/schema/usage.d.ts.map +1 -0
  64. package/dist/db/schema/usage.js +224 -0
  65. package/dist/db/schema/usage.js.map +1 -0
  66. package/dist/db/validate.d.ts +19 -0
  67. package/dist/db/validate.d.ts.map +1 -0
  68. package/dist/db/validate.js +58 -0
  69. package/dist/db/validate.js.map +1 -0
  70. package/dist/db/workspace-queries.d.ts +75 -0
  71. package/dist/db/workspace-queries.d.ts.map +1 -0
  72. package/dist/db/workspace-queries.js +78 -0
  73. package/dist/db/workspace-queries.js.map +1 -0
  74. package/dist/documents/classifier.d.ts +31 -0
  75. package/dist/documents/classifier.d.ts.map +1 -0
  76. package/dist/documents/classifier.js +41 -0
  77. package/dist/documents/classifier.js.map +1 -0
  78. package/dist/documents/errors.d.ts +32 -0
  79. package/dist/documents/errors.d.ts.map +1 -0
  80. package/dist/documents/errors.js +27 -0
  81. package/dist/documents/errors.js.map +1 -0
  82. package/dist/documents/index.d.ts +14 -0
  83. package/dist/documents/index.d.ts.map +1 -0
  84. package/dist/documents/index.js +13 -0
  85. package/dist/documents/index.js.map +1 -0
  86. package/dist/documents/service.d.ts +54 -0
  87. package/dist/documents/service.d.ts.map +1 -0
  88. package/dist/documents/service.js +146 -0
  89. package/dist/documents/service.js.map +1 -0
  90. package/dist/documents/types.d.ts +27 -0
  91. package/dist/documents/types.d.ts.map +1 -0
  92. package/dist/documents/types.js +5 -0
  93. package/dist/documents/types.js.map +1 -0
  94. package/dist/email/index.d.ts +5 -0
  95. package/dist/email/index.d.ts.map +1 -0
  96. package/dist/email/index.js +5 -0
  97. package/dist/email/index.js.map +1 -0
  98. package/dist/email/provider.d.ts +68 -0
  99. package/dist/email/provider.d.ts.map +1 -0
  100. package/dist/email/provider.js +199 -0
  101. package/dist/email/provider.js.map +1 -0
  102. package/dist/email/send.d.ts +47 -0
  103. package/dist/email/send.d.ts.map +1 -0
  104. package/dist/email/send.js +110 -0
  105. package/dist/email/send.js.map +1 -0
  106. package/dist/email/senders.d.ts +93 -0
  107. package/dist/email/senders.d.ts.map +1 -0
  108. package/dist/email/senders.js +232 -0
  109. package/dist/email/senders.js.map +1 -0
  110. package/dist/email/templates/base-layout.d.ts +7 -0
  111. package/dist/email/templates/base-layout.d.ts.map +1 -0
  112. package/dist/email/templates/base-layout.js +69 -0
  113. package/dist/email/templates/base-layout.js.map +1 -0
  114. package/dist/email/templates/index.d.ts +11 -0
  115. package/dist/email/templates/index.d.ts.map +1 -0
  116. package/dist/email/templates/index.js +17 -0
  117. package/dist/email/templates/index.js.map +1 -0
  118. package/dist/email/templates/password-reset.d.ts +7 -0
  119. package/dist/email/templates/password-reset.d.ts.map +1 -0
  120. package/dist/email/templates/password-reset.js +59 -0
  121. package/dist/email/templates/password-reset.js.map +1 -0
  122. package/dist/email/templates/payment-failed.d.ts +8 -0
  123. package/dist/email/templates/payment-failed.d.ts.map +1 -0
  124. package/dist/email/templates/payment-failed.js +62 -0
  125. package/dist/email/templates/payment-failed.js.map +1 -0
  126. package/dist/email/templates/quota-warning.d.ts +11 -0
  127. package/dist/email/templates/quota-warning.d.ts.map +1 -0
  128. package/dist/email/templates/quota-warning.js +82 -0
  129. package/dist/email/templates/quota-warning.js.map +1 -0
  130. package/dist/email/templates/subscription-confirmed.d.ts +9 -0
  131. package/dist/email/templates/subscription-confirmed.d.ts.map +1 -0
  132. package/dist/email/templates/subscription-confirmed.js +72 -0
  133. package/dist/email/templates/subscription-confirmed.js.map +1 -0
  134. package/dist/email/templates/trial-expiry.d.ts +9 -0
  135. package/dist/email/templates/trial-expiry.d.ts.map +1 -0
  136. package/dist/email/templates/trial-expiry.js +86 -0
  137. package/dist/email/templates/trial-expiry.js.map +1 -0
  138. package/dist/email/templates/trial-warning.d.ts +11 -0
  139. package/dist/email/templates/trial-warning.d.ts.map +1 -0
  140. package/dist/email/templates/trial-warning.js +70 -0
  141. package/dist/email/templates/trial-warning.js.map +1 -0
  142. package/dist/email/templates/verify-email.d.ts +7 -0
  143. package/dist/email/templates/verify-email.d.ts.map +1 -0
  144. package/dist/email/templates/verify-email.js +59 -0
  145. package/dist/email/templates/verify-email.js.map +1 -0
  146. package/dist/email/templates/welcome.d.ts +7 -0
  147. package/dist/email/templates/welcome.d.ts.map +1 -0
  148. package/dist/email/templates/welcome.js +51 -0
  149. package/dist/email/templates/welcome.js.map +1 -0
  150. package/dist/email/templates/workspace-invitation.d.ts +10 -0
  151. package/dist/email/templates/workspace-invitation.d.ts.map +1 -0
  152. package/dist/email/templates/workspace-invitation.js +79 -0
  153. package/dist/email/templates/workspace-invitation.js.map +1 -0
  154. package/dist/env.d.ts +21 -0
  155. package/dist/env.d.ts.map +1 -0
  156. package/dist/env.js +129 -0
  157. package/dist/env.js.map +1 -0
  158. package/dist/identity/audit.d.ts +39 -0
  159. package/dist/identity/audit.d.ts.map +1 -0
  160. package/dist/identity/audit.js +51 -0
  161. package/dist/identity/audit.js.map +1 -0
  162. package/dist/identity/errors.d.ts +32 -0
  163. package/dist/identity/errors.d.ts.map +1 -0
  164. package/dist/identity/errors.js +25 -0
  165. package/dist/identity/errors.js.map +1 -0
  166. package/dist/identity/index.d.ts +16 -0
  167. package/dist/identity/index.d.ts.map +1 -0
  168. package/dist/identity/index.js +15 -0
  169. package/dist/identity/index.js.map +1 -0
  170. package/dist/identity/service.d.ts +70 -0
  171. package/dist/identity/service.d.ts.map +1 -0
  172. package/dist/identity/service.js +179 -0
  173. package/dist/identity/service.js.map +1 -0
  174. package/dist/identity/types.d.ts +27 -0
  175. package/dist/identity/types.d.ts.map +1 -0
  176. package/dist/identity/types.js +5 -0
  177. package/dist/identity/types.js.map +1 -0
  178. package/dist/index.d.ts +3 -0
  179. package/dist/index.d.ts.map +1 -0
  180. package/dist/index.js +11 -0
  181. package/dist/index.js.map +1 -0
  182. package/dist/logger.d.ts +50 -0
  183. package/dist/logger.d.ts.map +1 -0
  184. package/dist/logger.js +183 -0
  185. package/dist/logger.js.map +1 -0
  186. package/dist/notifications/index.d.ts +86 -0
  187. package/dist/notifications/index.d.ts.map +1 -0
  188. package/dist/notifications/index.js +112 -0
  189. package/dist/notifications/index.js.map +1 -0
  190. package/dist/notifications/triggers.d.ts +68 -0
  191. package/dist/notifications/triggers.d.ts.map +1 -0
  192. package/dist/notifications/triggers.js +145 -0
  193. package/dist/notifications/triggers.js.map +1 -0
  194. package/dist/notifications/types.d.ts +19 -0
  195. package/dist/notifications/types.d.ts.map +1 -0
  196. package/dist/notifications/types.js +11 -0
  197. package/dist/notifications/types.js.map +1 -0
  198. package/package.json +101 -0
  199. package/src/db/migrations/0000_sharp_night_nurse.sql +52 -0
  200. package/src/db/migrations/0001_hesitant_vapor.sql +40 -0
  201. package/src/db/migrations/0002_numerous_lady_vermin.sql +235 -0
  202. package/src/db/migrations/0003_mighty_jean_grey.sql +37 -0
  203. package/src/db/migrations/0004_cynical_grandmaster.sql +2 -0
  204. package/src/db/migrations/0005_careful_switch.sql +8 -0
  205. package/src/db/migrations/0006_tense_blue_marvel.sql +16 -0
  206. package/src/db/migrations/0007_next_squadron_sinister.sql +1 -0
  207. package/src/db/migrations/0008_powerful_scream.sql +39 -0
  208. package/src/db/migrations/0009_sad_ink.sql +1 -0
  209. package/src/db/migrations/0010_shocking_omega_flight.sql +2 -0
  210. package/src/db/migrations/0011_ambiguous_otto_octavius.sql +151 -0
  211. package/src/db/migrations/0011_hnsw_vector_index.sql +17 -0
  212. package/src/db/migrations/0012_user_profiles.sql +30 -0
  213. package/src/db/migrations/0013_trending_seasonal_columns.sql +9 -0
  214. package/src/db/migrations/0014_add_product_type_to_agents.sql +6 -0
  215. package/src/db/migrations/0015_new_v1_tables.sql +149 -0
  216. package/src/db/migrations/0016_document_types.sql +30 -0
  217. package/src/db/migrations/0017_document_metadata.sql +2 -0
  218. package/src/db/migrations/0018_identity_graph.sql +200 -0
  219. package/src/db/migrations/0019_db_driven_prompts.sql +20 -0
  220. package/src/db/migrations/0020_pending_extractions.sql +35 -0
  221. package/src/db/migrations/0021_rag_documents.sql +39 -0
  222. package/src/db/migrations/0022_user_quotas_usage_jsonb.sql +70 -0
  223. package/src/db/migrations/0023_user_facts_user_scoped_unique.sql +17 -0
  224. package/src/db/migrations/0024_user_memories_hnsw.sql +23 -0
  225. package/src/db/migrations/0025_audit_trigger_errcode.sql +21 -0
  226. package/src/db/migrations/0026_user_profile_snapshots_composite_pk.sql +17 -0
  227. package/src/db/migrations/0027_billing_settings_mnt_columns.sql +54 -0
  228. package/src/db/migrations/0028_trial_credits_normalized_email.sql +37 -0
  229. package/src/db/migrations/0029_usage_records_conversation_idx.sql +13 -0
  230. package/src/db/migrations/0030_pending_extractions_composite_idx.sql +11 -0
  231. package/src/db/migrations/0031_rag_documents_hnsw.sql +16 -0
  232. package/src/db/migrations/0032_rate_limit_unique_idx.sql +16 -0
  233. package/src/db/migrations/0033_pending_extractions_partial_idx.sql +18 -0
  234. package/src/db/migrations/0034_rate_limit_count.sql +9 -0
  235. package/src/db/migrations/0035_credit_reservations.sql +24 -0
  236. package/src/db/migrations/0036_executions_audit_jobs.sql +96 -0
  237. package/src/db/migrations/0037_rate_limit_minute_bucket.sql +46 -0
  238. package/src/db/migrations/0038_user_quotas_generic_usage.sql +25 -0
  239. package/src/db/migrations/0039_platform_admin_impersonation.sql +27 -0
  240. package/src/db/migrations/0040_ee_audit_chain.sql +23 -0
  241. package/src/db/migrations/README.md +57 -0
  242. package/src/db/migrations/meta/0000_snapshot.json +348 -0
  243. package/src/db/migrations/meta/0001_snapshot.json +631 -0
  244. package/src/db/migrations/meta/0002_snapshot.json +2269 -0
  245. package/src/db/migrations/meta/0003_snapshot.json +2488 -0
  246. package/src/db/migrations/meta/0004_snapshot.json +2500 -0
  247. package/src/db/migrations/meta/0005_snapshot.json +2548 -0
  248. package/src/db/migrations/meta/0006_snapshot.json +2650 -0
  249. package/src/db/migrations/meta/0007_snapshot.json +2657 -0
  250. package/src/db/migrations/meta/0008_snapshot.json +2719 -0
  251. package/src/db/migrations/meta/0009_snapshot.json +2726 -0
  252. package/src/db/migrations/meta/0010_snapshot.json +2739 -0
  253. package/src/db/migrations/meta/0011_snapshot.json +3886 -0
  254. package/src/db/migrations/meta/_journal.json +300 -0
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Conversation & Message Persistence Service — Server-only
3
+ *
4
+ * Persistence-only: conversation lifecycle (create, list, read,
5
+ * rename, delete), message append/window reads, and vote state.
6
+ * Ported from the product application's conversation server actions
7
+ * and chat-route persistence helper (ADR-0009) — the tables
8
+ * (`conversations`, `messages`, `votes`) always lived in
9
+ * @intelligo-dev/core's schema, only the service layer sat in the product
10
+ * application.
11
+ *
12
+ * What did NOT come with it (stays product-side, native AI code):
13
+ * - LLM title generation (`generateTitleFromUserMessage` ran a
14
+ * Mastra agent inside an execution boundary — that's product
15
+ * logic over the execution boundary, not persistence).
16
+ * - Conversation windowing/summarization
17
+ * (applyConversationWindow, summarizeOldMessages) and the
18
+ * `conversationSummary` metadata write they produce.
19
+ * - Career's assessment-state read/write helpers
20
+ * (getAssessmentState/updateAssessmentState) — career-specific
21
+ * shape living under `conversations.metadata`.
22
+ * - Agent id validation ("career-advisor" | "sesh-planner") and
23
+ * default model selection — product decides both; this module
24
+ * accepts an already-resolved agentId/modelId.
25
+ *
26
+ * Callers pass a resolved actor (workspaceId, userId) rather than this
27
+ * module resolving one itself — @intelligo-dev/core cannot depend on
28
+ * @intelligo-dev/auth (see tests/architecture/dependency-direction.test.ts).
29
+ * Every query still filters by workspaceId AND userId internally
30
+ * (conversations are USER-PRIVATE within a workspace); the actor is
31
+ * never trusted to have done that itself.
32
+ *
33
+ * Failure is reported by throwing `ConversationServiceError` rather
34
+ * than returning a `{ success, error }` envelope — see ./errors.ts.
35
+ *
36
+ * This module is SERVER-ONLY. Do not import from client components.
37
+ */
38
+ import { and, asc, desc, eq, gt, inArray, lt, sql } from "drizzle-orm";
39
+ import { db } from "../db";
40
+ import { conversations, messages, votes } from "../db/schema";
41
+ import { ConversationServiceError } from "./errors";
42
+ // ---------------------------------------------------------------------------
43
+ // Internal helpers
44
+ // ---------------------------------------------------------------------------
45
+ /**
46
+ * Verify conversation ownership (workspaceId + userId) and return the
47
+ * row. Throws ConversationServiceError("not_found") otherwise — a
48
+ * conversation owned by someone else in the same workspace is
49
+ * indistinguishable from a conversation that doesn't exist, which is
50
+ * the point: the id alone should never reveal existence across users.
51
+ */
52
+ async function verifyConversation(actor, id) {
53
+ const [conversation] = await db
54
+ .select()
55
+ .from(conversations)
56
+ .where(and(eq(conversations.id, id), eq(conversations.workspaceId, actor.workspaceId), eq(conversations.userId, actor.userId)))
57
+ .limit(1);
58
+ if (!conversation) {
59
+ throw new ConversationServiceError("not_found", "Conversation not found");
60
+ }
61
+ return conversation;
62
+ }
63
+ // ---------------------------------------------------------------------------
64
+ // Conversation lifecycle
65
+ // ---------------------------------------------------------------------------
66
+ /**
67
+ * Create a new conversation.
68
+ *
69
+ * `agentId` and `modelId` are opaque to this module — the product
70
+ * decides which agents exist and which model backs a new conversation
71
+ * (see the module doc comment). `id` defaults to a fresh UUID when
72
+ * omitted.
73
+ */
74
+ export async function createConversation(actor, params) {
75
+ const [conversation] = await db
76
+ .insert(conversations)
77
+ .values({
78
+ id: params.id ?? crypto.randomUUID(),
79
+ workspaceId: actor.workspaceId,
80
+ userId: actor.userId,
81
+ agentId: params.agentId,
82
+ modelId: params.modelId,
83
+ title: params.title ?? null,
84
+ })
85
+ .returning();
86
+ if (!conversation) {
87
+ throw new ConversationServiceError("database_error", "Failed to create conversation");
88
+ }
89
+ return conversation;
90
+ }
91
+ /** Get a single conversation, scoped to the actor. */
92
+ export async function getConversation(actor, id) {
93
+ return verifyConversation(actor, id);
94
+ }
95
+ /**
96
+ * List conversations for the actor, most recently updated first.
97
+ * Projects only the columns a sidebar/dashboard list needs.
98
+ */
99
+ export async function listConversations(actor, options) {
100
+ const limit = options?.limit ?? 50;
101
+ const offset = options?.offset ?? 0;
102
+ return db
103
+ .select({
104
+ id: conversations.id,
105
+ title: conversations.title,
106
+ agentId: conversations.agentId,
107
+ updatedAt: conversations.updatedAt,
108
+ })
109
+ .from(conversations)
110
+ .where(and(eq(conversations.workspaceId, actor.workspaceId), eq(conversations.userId, actor.userId)))
111
+ .orderBy(desc(conversations.updatedAt))
112
+ .limit(limit)
113
+ .offset(offset);
114
+ }
115
+ const MAX_HISTORY_LIMIT = 100;
116
+ /** Paginated conversation history with cursor-based pagination. */
117
+ export async function getConversationHistory(actor, params) {
118
+ const limit = Math.min(Math.max(1, params.limit), MAX_HISTORY_LIMIT);
119
+ let cursorUpdatedAt;
120
+ if (params.endingBefore) {
121
+ // Scope the cursor lookup too: unscoped, it would answer "does
122
+ // this conversation id exist, and when was it updated?" for any
123
+ // id in any tenant — a cross-tenant existence oracle.
124
+ const [cursorConversation] = await db
125
+ .select({ updatedAt: conversations.updatedAt })
126
+ .from(conversations)
127
+ .where(and(eq(conversations.id, params.endingBefore), eq(conversations.workspaceId, actor.workspaceId), eq(conversations.userId, actor.userId)))
128
+ .limit(1);
129
+ cursorUpdatedAt = cursorConversation?.updatedAt;
130
+ }
131
+ const result = await db
132
+ .select()
133
+ .from(conversations)
134
+ .where(and(eq(conversations.workspaceId, actor.workspaceId), eq(conversations.userId, actor.userId), ...(cursorUpdatedAt
135
+ ? [lt(conversations.updatedAt, cursorUpdatedAt)]
136
+ : [])))
137
+ .orderBy(desc(conversations.updatedAt))
138
+ .limit(limit + 1);
139
+ const hasMore = result.length > limit;
140
+ const chats = hasMore ? result.slice(0, -1) : result;
141
+ return { chats, hasMore };
142
+ }
143
+ const MAX_TITLE_LENGTH = 300;
144
+ /** Rename a conversation. Throws on an empty title or a title that's too long. */
145
+ export async function renameConversation(actor, id, title) {
146
+ const trimmed = title.trim();
147
+ if (trimmed.length === 0) {
148
+ throw new ConversationServiceError("invalid_input", "Title cannot be empty");
149
+ }
150
+ if (trimmed.length > MAX_TITLE_LENGTH) {
151
+ throw new ConversationServiceError("invalid_input", `Title cannot exceed ${MAX_TITLE_LENGTH} characters`);
152
+ }
153
+ await verifyConversation(actor, id);
154
+ const [updated] = await db
155
+ .update(conversations)
156
+ .set({ title: trimmed, updatedAt: new Date() })
157
+ .where(eq(conversations.id, id))
158
+ .returning();
159
+ if (!updated) {
160
+ throw new ConversationServiceError("database_error", "Failed to rename conversation");
161
+ }
162
+ return updated;
163
+ }
164
+ /** Delete a conversation and all its messages (CASCADE). */
165
+ export async function deleteConversation(actor, id) {
166
+ await verifyConversation(actor, id);
167
+ await db.delete(conversations).where(eq(conversations.id, id));
168
+ }
169
+ /** Delete all conversations for the actor (user-scoped within the workspace). */
170
+ export async function deleteAllConversations(actor) {
171
+ const deleted = await db
172
+ .delete(conversations)
173
+ .where(and(eq(conversations.workspaceId, actor.workspaceId), eq(conversations.userId, actor.userId)))
174
+ .returning();
175
+ return { deletedCount: deleted.length };
176
+ }
177
+ // ---------------------------------------------------------------------------
178
+ // Messages
179
+ // ---------------------------------------------------------------------------
180
+ /** Get all messages for a conversation, ordered oldest-first. */
181
+ export async function getMessages(actor, conversationId) {
182
+ await verifyConversation(actor, conversationId);
183
+ return db
184
+ .select()
185
+ .from(messages)
186
+ .where(eq(messages.conversationId, conversationId))
187
+ .orderBy(asc(messages.createdAt));
188
+ }
189
+ /**
190
+ * Save a batch of messages. Every message's `conversationId` must
191
+ * belong to a conversation the actor owns — this is a directly
192
+ * reachable persistence entry point (through a Server Action or
193
+ * route), not a background job with its own scope check, so a
194
+ * workspace-only guard here would let any member insert content into
195
+ * a colleague's private conversation. Bumps `updatedAt` on the
196
+ * touched conversations.
197
+ */
198
+ export async function saveMessages(actor, messagesToSave) {
199
+ if (messagesToSave.length === 0)
200
+ return [];
201
+ const conversationIds = [
202
+ ...new Set(messagesToSave.map((m) => m.conversationId)),
203
+ ];
204
+ const ownedConversations = await db
205
+ .select({ id: conversations.id })
206
+ .from(conversations)
207
+ .where(and(eq(conversations.workspaceId, actor.workspaceId), eq(conversations.userId, actor.userId), inArray(conversations.id, conversationIds)));
208
+ const ownedIds = new Set(ownedConversations.map((c) => c.id));
209
+ const unowned = messagesToSave.filter((m) => !ownedIds.has(m.conversationId));
210
+ if (unowned.length > 0) {
211
+ throw new ConversationServiceError("forbidden", "One or more messages reference a conversation the actor does not own");
212
+ }
213
+ // A batch arrives in conversation order, but `defaultNow()` would
214
+ // stamp every row with the same transaction time, and a tie in
215
+ // created_at makes "the messages after this one" undefined — a
216
+ // regenerate could then delete nothing or everything. One millisecond
217
+ // per position keeps the order the caller gave.
218
+ const base = Date.now();
219
+ const stamped = messagesToSave.map((message, index) => ({
220
+ ...message,
221
+ createdAt: message.createdAt ?? new Date(base + index),
222
+ }));
223
+ const inserted = await db.insert(messages).values(stamped).returning();
224
+ await db
225
+ .update(conversations)
226
+ .set({ updatedAt: new Date() })
227
+ .where(inArray(conversations.id, conversationIds));
228
+ return inserted;
229
+ }
230
+ /**
231
+ * Upsert streamed messages after completion — transport-adjacent
232
+ * batched upsert, ported from the chat route's post-stream
233
+ * persistence step. No actor param: by the time a caller reaches this
234
+ * (mid- or post-stream, inside an already-authorized request) the
235
+ * conversation's ownership was already verified once (typically via
236
+ * `getConversation`/`getMessages` at the top of the request); this
237
+ * function's job is the idempotent write, not a second ownership
238
+ * check per message.
239
+ *
240
+ * Uses a single multi-row `INSERT ... ON CONFLICT (id) DO UPDATE` so
241
+ * an N-message tool loop pays one round-trip instead of 2N. The
242
+ * `WHERE` clause on the DO UPDATE keeps the write scoped to
243
+ * `conversationId` even if a message id collided across
244
+ * conversations (defended against, not expected under normal id
245
+ * generation).
246
+ */
247
+ export async function upsertMessages(conversationId, finishedMessages) {
248
+ if (finishedMessages.length === 0)
249
+ return;
250
+ // Increment per row so `ORDER BY created_at` (used by getMessages)
251
+ // preserves insertion order across a batched tool loop. Sharing a
252
+ // single `new Date()` across rows would tie their timestamps and
253
+ // let Postgres return them in heap order.
254
+ const baseMs = Date.now();
255
+ const rows = finishedMessages.map((m, i) => ({
256
+ id: m.id,
257
+ conversationId,
258
+ role: m.role,
259
+ parts: JSON.stringify(m.parts),
260
+ createdAt: new Date(baseMs + i),
261
+ }));
262
+ await db
263
+ .insert(messages)
264
+ .values(rows)
265
+ .onConflictDoUpdate({
266
+ target: messages.id,
267
+ set: {
268
+ parts: sql `excluded.parts`,
269
+ },
270
+ setWhere: sql `${messages.conversationId} = excluded.conversation_id`,
271
+ });
272
+ }
273
+ /** Update a message's parts (e.g. for a tool-approval flow). */
274
+ export async function updateMessage(actor, params) {
275
+ const [message] = await db
276
+ .select()
277
+ .from(messages)
278
+ .where(eq(messages.id, params.id))
279
+ .limit(1);
280
+ if (!message) {
281
+ throw new ConversationServiceError("not_found", "Message not found");
282
+ }
283
+ await verifyConversation(actor, message.conversationId);
284
+ const [updated] = await db
285
+ .update(messages)
286
+ .set({ parts: JSON.stringify(params.parts) })
287
+ .where(eq(messages.id, params.id))
288
+ .returning();
289
+ if (!updated) {
290
+ throw new ConversationServiceError("database_error", "Failed to update message");
291
+ }
292
+ return updated;
293
+ }
294
+ /** Delete all messages created after a specific message. */
295
+ export async function deleteTrailingMessages(actor, params) {
296
+ const [message] = await db
297
+ .select()
298
+ .from(messages)
299
+ .where(eq(messages.id, params.id))
300
+ .limit(1);
301
+ if (!message) {
302
+ throw new ConversationServiceError("not_found", "Message not found");
303
+ }
304
+ await verifyConversation(actor, message.conversationId);
305
+ // Compare inside SQL rather than against the JS Date read above: the
306
+ // driver truncates Postgres microseconds to milliseconds, so a message
307
+ // whose real timestamp is 12:00:00.123456 reads back as .123, and
308
+ // `created_at > .123` then matches the message itself — deleting the
309
+ // row a regenerate was supposed to keep.
310
+ const deleted = await db
311
+ .delete(messages)
312
+ .where(and(eq(messages.conversationId, message.conversationId), gt(messages.createdAt, sql `(select ${messages.createdAt} from ${messages} where ${messages.id} = ${params.id})`)))
313
+ .returning();
314
+ return { deletedCount: deleted.length };
315
+ }
316
+ // ---------------------------------------------------------------------------
317
+ // Votes
318
+ // ---------------------------------------------------------------------------
319
+ /** Vote on a message (upvote or downvote). */
320
+ export async function voteMessage(actor, params) {
321
+ await verifyConversation(actor, params.chatId);
322
+ const [message] = await db
323
+ .select({ id: messages.id })
324
+ .from(messages)
325
+ .where(and(eq(messages.id, params.messageId), eq(messages.conversationId, params.chatId)))
326
+ .limit(1);
327
+ if (!message) {
328
+ throw new ConversationServiceError("not_found", "Message not found");
329
+ }
330
+ await db
331
+ .insert(votes)
332
+ .values({
333
+ chatId: params.chatId,
334
+ messageId: params.messageId,
335
+ isUpvoted: params.type === "up",
336
+ })
337
+ .onConflictDoUpdate({
338
+ target: [votes.chatId, votes.messageId],
339
+ set: { isUpvoted: params.type === "up" },
340
+ });
341
+ }
342
+ /** Get all votes for a conversation. */
343
+ export async function getVotes(actor, chatId) {
344
+ await verifyConversation(actor, chatId);
345
+ return db.select().from(votes).where(eq(votes.chatId, chatId));
346
+ }
347
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/conversations/service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Shared types for the conversations service.
3
+ */
4
+ /** Resolved actor identity — every query is scoped to this pair. */
5
+ export type ConversationActor = {
6
+ workspaceId: string;
7
+ userId: string;
8
+ };
9
+ export type { Conversation, InsertConversation, Message, InsertMessage, Vote, InsertVote, ConversationMetadata, } from "../db/schema";
10
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/conversations/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,oEAAoE;AACpE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,OAAO,EACP,aAAa,EACb,IAAI,EACJ,UAAU,EACV,oBAAoB,GACrB,MAAM,cAAc,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Shared types for the conversations service.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/conversations/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Database Client
3
+ *
4
+ * Uses different drivers based on environment:
5
+ * - Local development: postgres driver (node-postgres)
6
+ * - Production (Neon): @neondatabase/serverless driver
7
+ *
8
+ * The client is initialized lazily on first method access so that
9
+ * `next build` can statically collect API route metadata without
10
+ * having DATABASE_URL available. The throw still happens at runtime
11
+ * for any request that actually touches the DB, so a missing env in
12
+ * production surfaces loudly on the first query.
13
+ */
14
+ import { drizzle as drizzleNeon } from "drizzle-orm/neon-http";
15
+ import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres";
16
+ type Db = ReturnType<typeof drizzleNeon> | ReturnType<typeof drizzlePostgres>;
17
+ export declare const db: Db;
18
+ export {};
19
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/db/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAMvE,KAAK,EAAE,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAkC9E,eAAO,MAAM,EAAE,EAAE,EAMT,CAAC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Database Client
3
+ *
4
+ * Uses different drivers based on environment:
5
+ * - Local development: postgres driver (node-postgres)
6
+ * - Production (Neon): @neondatabase/serverless driver
7
+ *
8
+ * The client is initialized lazily on first method access so that
9
+ * `next build` can statically collect API route metadata without
10
+ * having DATABASE_URL available. The throw still happens at runtime
11
+ * for any request that actually touches the DB, so a missing env in
12
+ * production surfaces loudly on the first query.
13
+ */
14
+ import { drizzle as drizzleNeon } from "drizzle-orm/neon-http";
15
+ import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres";
16
+ import { neon } from "@neondatabase/serverless";
17
+ import pkg from "pg";
18
+ const { Pool } = pkg;
19
+ import * as schema from "./schema";
20
+ let cached = null;
21
+ function initDb() {
22
+ const url = process.env.DATABASE_URL;
23
+ if (!url) {
24
+ throw new Error("DATABASE_URL is not defined. Please set it in your .env file.");
25
+ }
26
+ const isNeon = url.includes("neon.tech") || url.includes("@ep-");
27
+ if (isNeon) {
28
+ const sql = neon(url);
29
+ console.log("📊 Database: Neon (serverless)");
30
+ return drizzleNeon(sql, { schema });
31
+ }
32
+ const pool = new Pool({ connectionString: url });
33
+ console.log("📊 Database: PostgreSQL (local)");
34
+ return drizzlePostgres(pool, { schema });
35
+ }
36
+ function resolveDb() {
37
+ if (!cached)
38
+ cached = initDb();
39
+ return cached;
40
+ }
41
+ // Proxy keeps the ergonomic `db.select()...` shape for all call
42
+ // sites while deferring actual driver instantiation to first access.
43
+ // `next build` never touches these handlers, so module load is
44
+ // env-independent; first real request materializes the client.
45
+ export const db = new Proxy({}, {
46
+ get(_target, prop, receiver) {
47
+ const real = resolveDb();
48
+ const value = Reflect.get(real, prop, receiver);
49
+ return typeof value === "function" ? value.bind(real) : value;
50
+ },
51
+ });
52
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/db/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAChD,OAAO,GAAG,MAAM,IAAI,CAAC;AACrB,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC;AACrB,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AAInC,IAAI,MAAM,GAAc,IAAI,CAAC;AAE7B,SAAS,MAAM;IACb,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IACrC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,+DAA+D,CAChE,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAEjE,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;QAC9C,OAAO,WAAW,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;IAC/C,OAAO,eAAe,CAAC,IAAW,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,SAAS;IAChB,IAAI,CAAC,MAAM;QAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC/B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,gEAAgE;AAChE,qEAAqE;AACrE,+DAA+D;AAC/D,+DAA+D;AAC/D,MAAM,CAAC,MAAM,EAAE,GAAO,IAAI,KAAK,CAAC,EAAQ,EAAE;IACxC,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ;QACzB,MAAM,IAAI,GAAG,SAAS,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QAChD,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAChE,CAAC;CACF,CAAO,CAAC"}
@@ -0,0 +1,4 @@
1
+ export * from "./client";
2
+ export * from "./validate";
3
+ export { workspaceEq, withWorkspaceFilter } from "./workspace-queries";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":"AACA,cAAc,UAAU,CAAC;AAGzB,cAAc,YAAY,CAAC;AAG3B,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,8 @@
1
+ // Re-export database client
2
+ export * from "./client";
3
+ // Re-export database validation utilities
4
+ export * from "./validate";
5
+ // Re-export workspace-scoped query helpers (Phase 10)
6
+ export { workspaceEq, withWorkspaceFilter } from "./workspace-queries";
7
+ // This file will later also re-export schema types and helpers
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAC5B,cAAc,UAAU,CAAC;AAEzB,0CAA0C;AAC1C,cAAc,YAAY,CAAC;AAE3B,sDAAsD;AACtD,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAEvE,+DAA+D"}