@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.
- package/LICENSE +201 -0
- package/dist/conversations/errors.d.ts +31 -0
- package/dist/conversations/errors.d.ts.map +1 -0
- package/dist/conversations/errors.js +25 -0
- package/dist/conversations/errors.js.map +1 -0
- package/dist/conversations/index.d.ts +13 -0
- package/dist/conversations/index.d.ts.map +1 -0
- package/dist/conversations/index.js +12 -0
- package/dist/conversations/index.js.map +1 -0
- package/dist/conversations/service.d.ts +132 -0
- package/dist/conversations/service.d.ts.map +1 -0
- package/dist/conversations/service.js +347 -0
- package/dist/conversations/service.js.map +1 -0
- package/dist/conversations/types.d.ts +10 -0
- package/dist/conversations/types.d.ts.map +1 -0
- package/dist/conversations/types.js +5 -0
- package/dist/conversations/types.js.map +1 -0
- package/dist/db/client.d.ts +19 -0
- package/dist/db/client.d.ts.map +1 -0
- package/dist/db/client.js +52 -0
- package/dist/db/client.js.map +1 -0
- package/dist/db/index.d.ts +4 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +8 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema/agents.d.ts +400 -0
- package/dist/db/schema/agents.d.ts.map +1 -0
- package/dist/db/schema/agents.js +39 -0
- package/dist/db/schema/agents.js.map +1 -0
- package/dist/db/schema/ai.d.ts +2222 -0
- package/dist/db/schema/ai.d.ts.map +1 -0
- package/dist/db/schema/ai.js +313 -0
- package/dist/db/schema/ai.js.map +1 -0
- package/dist/db/schema/auth.d.ts +1222 -0
- package/dist/db/schema/auth.d.ts.map +1 -0
- package/dist/db/schema/auth.js +162 -0
- package/dist/db/schema/auth.js.map +1 -0
- package/dist/db/schema/billing.d.ts +1199 -0
- package/dist/db/schema/billing.d.ts.map +1 -0
- package/dist/db/schema/billing.js +150 -0
- package/dist/db/schema/billing.js.map +1 -0
- package/dist/db/schema/feature-flags.d.ts +143 -0
- package/dist/db/schema/feature-flags.d.ts.map +1 -0
- package/dist/db/schema/feature-flags.js +25 -0
- package/dist/db/schema/feature-flags.js.map +1 -0
- package/dist/db/schema/identity.d.ts +1295 -0
- package/dist/db/schema/identity.d.ts.map +1 -0
- package/dist/db/schema/identity.js +258 -0
- package/dist/db/schema/identity.js.map +1 -0
- package/dist/db/schema/index.d.ts +16 -0
- package/dist/db/schema/index.d.ts.map +1 -0
- package/dist/db/schema/index.js +43 -0
- package/dist/db/schema/index.js.map +1 -0
- package/dist/db/schema/notifications.d.ts +182 -0
- package/dist/db/schema/notifications.d.ts.map +1 -0
- package/dist/db/schema/notifications.js +38 -0
- package/dist/db/schema/notifications.js.map +1 -0
- package/dist/db/schema/rag.d.ts +171 -0
- package/dist/db/schema/rag.d.ts.map +1 -0
- package/dist/db/schema/rag.js +42 -0
- package/dist/db/schema/rag.js.map +1 -0
- package/dist/db/schema/usage.d.ts +1193 -0
- package/dist/db/schema/usage.d.ts.map +1 -0
- package/dist/db/schema/usage.js +224 -0
- package/dist/db/schema/usage.js.map +1 -0
- package/dist/db/validate.d.ts +19 -0
- package/dist/db/validate.d.ts.map +1 -0
- package/dist/db/validate.js +58 -0
- package/dist/db/validate.js.map +1 -0
- package/dist/db/workspace-queries.d.ts +75 -0
- package/dist/db/workspace-queries.d.ts.map +1 -0
- package/dist/db/workspace-queries.js +78 -0
- package/dist/db/workspace-queries.js.map +1 -0
- package/dist/documents/classifier.d.ts +31 -0
- package/dist/documents/classifier.d.ts.map +1 -0
- package/dist/documents/classifier.js +41 -0
- package/dist/documents/classifier.js.map +1 -0
- package/dist/documents/errors.d.ts +32 -0
- package/dist/documents/errors.d.ts.map +1 -0
- package/dist/documents/errors.js +27 -0
- package/dist/documents/errors.js.map +1 -0
- package/dist/documents/index.d.ts +14 -0
- package/dist/documents/index.d.ts.map +1 -0
- package/dist/documents/index.js +13 -0
- package/dist/documents/index.js.map +1 -0
- package/dist/documents/service.d.ts +54 -0
- package/dist/documents/service.d.ts.map +1 -0
- package/dist/documents/service.js +146 -0
- package/dist/documents/service.js.map +1 -0
- package/dist/documents/types.d.ts +27 -0
- package/dist/documents/types.d.ts.map +1 -0
- package/dist/documents/types.js +5 -0
- package/dist/documents/types.js.map +1 -0
- package/dist/email/index.d.ts +5 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/email/index.js +5 -0
- package/dist/email/index.js.map +1 -0
- package/dist/email/provider.d.ts +68 -0
- package/dist/email/provider.d.ts.map +1 -0
- package/dist/email/provider.js +199 -0
- package/dist/email/provider.js.map +1 -0
- package/dist/email/send.d.ts +47 -0
- package/dist/email/send.d.ts.map +1 -0
- package/dist/email/send.js +110 -0
- package/dist/email/send.js.map +1 -0
- package/dist/email/senders.d.ts +93 -0
- package/dist/email/senders.d.ts.map +1 -0
- package/dist/email/senders.js +232 -0
- package/dist/email/senders.js.map +1 -0
- package/dist/email/templates/base-layout.d.ts +7 -0
- package/dist/email/templates/base-layout.d.ts.map +1 -0
- package/dist/email/templates/base-layout.js +69 -0
- package/dist/email/templates/base-layout.js.map +1 -0
- package/dist/email/templates/index.d.ts +11 -0
- package/dist/email/templates/index.d.ts.map +1 -0
- package/dist/email/templates/index.js +17 -0
- package/dist/email/templates/index.js.map +1 -0
- package/dist/email/templates/password-reset.d.ts +7 -0
- package/dist/email/templates/password-reset.d.ts.map +1 -0
- package/dist/email/templates/password-reset.js +59 -0
- package/dist/email/templates/password-reset.js.map +1 -0
- package/dist/email/templates/payment-failed.d.ts +8 -0
- package/dist/email/templates/payment-failed.d.ts.map +1 -0
- package/dist/email/templates/payment-failed.js +62 -0
- package/dist/email/templates/payment-failed.js.map +1 -0
- package/dist/email/templates/quota-warning.d.ts +11 -0
- package/dist/email/templates/quota-warning.d.ts.map +1 -0
- package/dist/email/templates/quota-warning.js +82 -0
- package/dist/email/templates/quota-warning.js.map +1 -0
- package/dist/email/templates/subscription-confirmed.d.ts +9 -0
- package/dist/email/templates/subscription-confirmed.d.ts.map +1 -0
- package/dist/email/templates/subscription-confirmed.js +72 -0
- package/dist/email/templates/subscription-confirmed.js.map +1 -0
- package/dist/email/templates/trial-expiry.d.ts +9 -0
- package/dist/email/templates/trial-expiry.d.ts.map +1 -0
- package/dist/email/templates/trial-expiry.js +86 -0
- package/dist/email/templates/trial-expiry.js.map +1 -0
- package/dist/email/templates/trial-warning.d.ts +11 -0
- package/dist/email/templates/trial-warning.d.ts.map +1 -0
- package/dist/email/templates/trial-warning.js +70 -0
- package/dist/email/templates/trial-warning.js.map +1 -0
- package/dist/email/templates/verify-email.d.ts +7 -0
- package/dist/email/templates/verify-email.d.ts.map +1 -0
- package/dist/email/templates/verify-email.js +59 -0
- package/dist/email/templates/verify-email.js.map +1 -0
- package/dist/email/templates/welcome.d.ts +7 -0
- package/dist/email/templates/welcome.d.ts.map +1 -0
- package/dist/email/templates/welcome.js +51 -0
- package/dist/email/templates/welcome.js.map +1 -0
- package/dist/email/templates/workspace-invitation.d.ts +10 -0
- package/dist/email/templates/workspace-invitation.d.ts.map +1 -0
- package/dist/email/templates/workspace-invitation.js +79 -0
- package/dist/email/templates/workspace-invitation.js.map +1 -0
- package/dist/env.d.ts +21 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +129 -0
- package/dist/env.js.map +1 -0
- package/dist/identity/audit.d.ts +39 -0
- package/dist/identity/audit.d.ts.map +1 -0
- package/dist/identity/audit.js +51 -0
- package/dist/identity/audit.js.map +1 -0
- package/dist/identity/errors.d.ts +32 -0
- package/dist/identity/errors.d.ts.map +1 -0
- package/dist/identity/errors.js +25 -0
- package/dist/identity/errors.js.map +1 -0
- package/dist/identity/index.d.ts +16 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/index.js +15 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/identity/service.d.ts +70 -0
- package/dist/identity/service.d.ts.map +1 -0
- package/dist/identity/service.js +179 -0
- package/dist/identity/service.js.map +1 -0
- package/dist/identity/types.d.ts +27 -0
- package/dist/identity/types.d.ts.map +1 -0
- package/dist/identity/types.js +5 -0
- package/dist/identity/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +50 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +183 -0
- package/dist/logger.js.map +1 -0
- package/dist/notifications/index.d.ts +86 -0
- package/dist/notifications/index.d.ts.map +1 -0
- package/dist/notifications/index.js +112 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/notifications/triggers.d.ts +68 -0
- package/dist/notifications/triggers.d.ts.map +1 -0
- package/dist/notifications/triggers.js +145 -0
- package/dist/notifications/triggers.js.map +1 -0
- package/dist/notifications/types.d.ts +19 -0
- package/dist/notifications/types.d.ts.map +1 -0
- package/dist/notifications/types.js +11 -0
- package/dist/notifications/types.js.map +1 -0
- package/package.json +101 -0
- package/src/db/migrations/0000_sharp_night_nurse.sql +52 -0
- package/src/db/migrations/0001_hesitant_vapor.sql +40 -0
- package/src/db/migrations/0002_numerous_lady_vermin.sql +235 -0
- package/src/db/migrations/0003_mighty_jean_grey.sql +37 -0
- package/src/db/migrations/0004_cynical_grandmaster.sql +2 -0
- package/src/db/migrations/0005_careful_switch.sql +8 -0
- package/src/db/migrations/0006_tense_blue_marvel.sql +16 -0
- package/src/db/migrations/0007_next_squadron_sinister.sql +1 -0
- package/src/db/migrations/0008_powerful_scream.sql +39 -0
- package/src/db/migrations/0009_sad_ink.sql +1 -0
- package/src/db/migrations/0010_shocking_omega_flight.sql +2 -0
- package/src/db/migrations/0011_ambiguous_otto_octavius.sql +151 -0
- package/src/db/migrations/0011_hnsw_vector_index.sql +17 -0
- package/src/db/migrations/0012_user_profiles.sql +30 -0
- package/src/db/migrations/0013_trending_seasonal_columns.sql +9 -0
- package/src/db/migrations/0014_add_product_type_to_agents.sql +6 -0
- package/src/db/migrations/0015_new_v1_tables.sql +149 -0
- package/src/db/migrations/0016_document_types.sql +30 -0
- package/src/db/migrations/0017_document_metadata.sql +2 -0
- package/src/db/migrations/0018_identity_graph.sql +200 -0
- package/src/db/migrations/0019_db_driven_prompts.sql +20 -0
- package/src/db/migrations/0020_pending_extractions.sql +35 -0
- package/src/db/migrations/0021_rag_documents.sql +39 -0
- package/src/db/migrations/0022_user_quotas_usage_jsonb.sql +70 -0
- package/src/db/migrations/0023_user_facts_user_scoped_unique.sql +17 -0
- package/src/db/migrations/0024_user_memories_hnsw.sql +23 -0
- package/src/db/migrations/0025_audit_trigger_errcode.sql +21 -0
- package/src/db/migrations/0026_user_profile_snapshots_composite_pk.sql +17 -0
- package/src/db/migrations/0027_billing_settings_mnt_columns.sql +54 -0
- package/src/db/migrations/0028_trial_credits_normalized_email.sql +37 -0
- package/src/db/migrations/0029_usage_records_conversation_idx.sql +13 -0
- package/src/db/migrations/0030_pending_extractions_composite_idx.sql +11 -0
- package/src/db/migrations/0031_rag_documents_hnsw.sql +16 -0
- package/src/db/migrations/0032_rate_limit_unique_idx.sql +16 -0
- package/src/db/migrations/0033_pending_extractions_partial_idx.sql +18 -0
- package/src/db/migrations/0034_rate_limit_count.sql +9 -0
- package/src/db/migrations/0035_credit_reservations.sql +24 -0
- package/src/db/migrations/0036_executions_audit_jobs.sql +96 -0
- package/src/db/migrations/0037_rate_limit_minute_bucket.sql +46 -0
- package/src/db/migrations/0038_user_quotas_generic_usage.sql +25 -0
- package/src/db/migrations/0039_platform_admin_impersonation.sql +27 -0
- package/src/db/migrations/0040_ee_audit_chain.sql +23 -0
- package/src/db/migrations/README.md +57 -0
- package/src/db/migrations/meta/0000_snapshot.json +348 -0
- package/src/db/migrations/meta/0001_snapshot.json +631 -0
- package/src/db/migrations/meta/0002_snapshot.json +2269 -0
- package/src/db/migrations/meta/0003_snapshot.json +2488 -0
- package/src/db/migrations/meta/0004_snapshot.json +2500 -0
- package/src/db/migrations/meta/0005_snapshot.json +2548 -0
- package/src/db/migrations/meta/0006_snapshot.json +2650 -0
- package/src/db/migrations/meta/0007_snapshot.json +2657 -0
- package/src/db/migrations/meta/0008_snapshot.json +2719 -0
- package/src/db/migrations/meta/0009_snapshot.json +2726 -0
- package/src/db/migrations/meta/0010_snapshot.json +2739 -0
- package/src/db/migrations/meta/0011_snapshot.json +3886 -0
- 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 @@
|
|
|
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 @@
|
|
|
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"}
|
package/dist/db/index.js
ADDED
|
@@ -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"}
|