@firstlovecenter/ai-chat 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.
@@ -0,0 +1,163 @@
1
+ 'use strict';
2
+
3
+ // src/adapters/prisma/adapter.ts
4
+ var toSession = (row) => ({
5
+ id: Number(row.id),
6
+ userId: Number(row.userId),
7
+ title: row.title,
8
+ createdAt: row.createdAt,
9
+ updatedAt: row.updatedAt
10
+ });
11
+ var toMessage = (row) => ({
12
+ id: Number(row.id),
13
+ sessionId: Number(row.sessionId),
14
+ // Stored as VARCHAR(16) but constrained to ChatMessageRole at write time.
15
+ role: row.role,
16
+ question: row.question,
17
+ blocks: row.blocks,
18
+ prose: row.prose,
19
+ errorJson: row.errorJson,
20
+ createdAt: row.createdAt
21
+ });
22
+ var toAiSettings = (row) => ({
23
+ toolProvider: row.toolProvider,
24
+ gcpLocation: row.gcpLocation,
25
+ chatInterface: row.chatInterface,
26
+ updatedAt: row.updatedAt,
27
+ updatedByUserId: row.updatedByUserId === null ? null : Number(row.updatedByUserId)
28
+ });
29
+ var DEFAULT_AI_SETTINGS = {
30
+ toolProvider: "claude",
31
+ gcpLocation: "us-east5",
32
+ chatInterface: "custom",
33
+ updatedAt: null,
34
+ updatedByUserId: null
35
+ };
36
+ function createPrismaPersistence(prisma) {
37
+ return {
38
+ // Sessions ---------------------------------------------------------------
39
+ async createSession(input) {
40
+ const row = await prisma.chatSession.create({
41
+ data: { userId: input.userId, title: input.title }
42
+ });
43
+ return toSession(row);
44
+ },
45
+ async getSession(id, userId) {
46
+ const row = await prisma.chatSession.findFirst({ where: { id, userId } });
47
+ return row ? toSession(row) : null;
48
+ },
49
+ async listSessionsForUser(userId, opts) {
50
+ const rows = await prisma.chatSession.findMany({
51
+ where: { userId },
52
+ orderBy: { updatedAt: "desc" },
53
+ take: opts?.limit
54
+ });
55
+ return rows.map(toSession);
56
+ },
57
+ async updateSession(id, userId, patch) {
58
+ await prisma.chatSession.updateMany({
59
+ where: { id, userId },
60
+ data: { ...patch, updatedAt: /* @__PURE__ */ new Date() }
61
+ });
62
+ },
63
+ async deleteSession(id, userId) {
64
+ await prisma.chatSession.deleteMany({ where: { id, userId } });
65
+ },
66
+ // Messages ---------------------------------------------------------------
67
+ async appendMessage(input) {
68
+ const row = await prisma.chatMessage.create({
69
+ data: {
70
+ sessionId: input.sessionId,
71
+ role: input.role,
72
+ question: input.question ?? null,
73
+ blocks: input.blocks ?? null,
74
+ prose: input.prose ?? null,
75
+ errorJson: input.errorJson ?? null
76
+ }
77
+ });
78
+ return toMessage(row);
79
+ },
80
+ async listMessagesForSession(sessionId, userId) {
81
+ const session = await prisma.chatSession.findFirst({
82
+ where: { id: sessionId, userId }
83
+ });
84
+ if (!session) return [];
85
+ const rows = await prisma.chatMessage.findMany({
86
+ where: { sessionId },
87
+ orderBy: { createdAt: "asc" }
88
+ });
89
+ return rows.map(toMessage);
90
+ },
91
+ // AI settings (singleton row at id=1) -----------------------------------
92
+ async getAiSettings() {
93
+ const row = await prisma.aiSettings.findUnique({ where: { id: 1 } });
94
+ return row ? toAiSettings(row) : { ...DEFAULT_AI_SETTINGS };
95
+ },
96
+ async updateAiSettings(patch, byUserId) {
97
+ const row = await prisma.aiSettings.upsert({
98
+ where: { id: 1 },
99
+ create: { id: 1, ...patch, updatedByUserId: byUserId },
100
+ update: { ...patch, updatedByUserId: byUserId }
101
+ });
102
+ return toAiSettings(row);
103
+ }
104
+ };
105
+ }
106
+
107
+ // src/adapters/prisma/schema.ts
108
+ var prismaSchemaFragment = `// =============================================================================
109
+ // @firstlovecenter/ai-chat \u2014 Prisma schema fragment
110
+ // =============================================================================
111
+ //
112
+ // HOSTS: copy the three models below into your own \`schema.prisma\`, then run
113
+ //
114
+ // pnpm prisma generate
115
+ // pnpm prisma migrate dev --name flc_ai_chat
116
+ //
117
+ // Column names, SQL types, and indexes match the Drizzle adapter shipped at
118
+ // \`@firstlovecenter/ai-chat/server/drizzle\` byte-for-byte, so the underlying MySQL schema
119
+ // is identical regardless of which ORM your host picks. A host could swap
120
+ // from one adapter to the other without touching data.
121
+ // =============================================================================
122
+
123
+ model ChatSession {
124
+ id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
125
+ userId BigInt @map("user_id") @db.UnsignedBigInt
126
+ title String @db.VarChar(200)
127
+ createdAt DateTime @default(now()) @map("created_at") @db.DateTime(0)
128
+ updatedAt DateTime @default(now()) @map("updated_at") @db.DateTime(0)
129
+
130
+ @@index([userId, updatedAt], map: "idx_chat_session_user_updated")
131
+ @@map("chat_sessions")
132
+ }
133
+
134
+ model ChatMessage {
135
+ id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
136
+ sessionId BigInt @map("session_id") @db.UnsignedBigInt
137
+ role String @db.VarChar(16)
138
+ question String? @db.Text
139
+ blocks Json?
140
+ prose Json?
141
+ errorJson Json? @map("error_json")
142
+ createdAt DateTime @default(now()) @map("created_at") @db.DateTime(0)
143
+
144
+ @@index([sessionId, createdAt], map: "idx_chat_msg_session_created")
145
+ @@map("chat_messages")
146
+ }
147
+
148
+ model AiSettings {
149
+ id Int @id @default(1) @db.UnsignedTinyInt
150
+ toolProvider String @default("claude") @map("tool_provider") @db.VarChar(32)
151
+ gcpLocation String @default("us-east5") @map("gcp_location") @db.VarChar(32)
152
+ chatInterface String @default("custom") @map("chat_interface") @db.VarChar(32)
153
+ updatedAt DateTime @default(now()) @map("updated_at") @db.DateTime(0)
154
+ updatedByUserId BigInt? @map("updated_by_user_id") @db.UnsignedBigInt
155
+
156
+ @@map("ai_settings")
157
+ }
158
+ `;
159
+
160
+ exports.createPrismaPersistence = createPrismaPersistence;
161
+ exports.prismaSchemaFragment = prismaSchemaFragment;
162
+ //# sourceMappingURL=index.cjs.map
163
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/prisma/adapter.ts","../../src/adapters/prisma/schema.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Prisma `PersistencePort` adapter.\n *\n * Hosts call `createPrismaPersistence(prisma)` with their own configured\n * `PrismaClient` and pass the result to `configureAiChat({ persistence, ... })`.\n *\n * The concrete `PrismaClient` is an OPTIONAL peer dep, so this module never\n * imports a runtime value from `@prisma/client`. Instead it accepts a\n * structural duck-typed shape covering only the delegate methods the port\n * uses. Any object that satisfies `PrismaLike` works — including a real\n * `PrismaClient`, a Prisma extension proxy, or a hand-rolled fake in tests.\n *\n * BigInt note: Prisma returns row IDs as JavaScript `BigInt`. Domain types\n * use plain `number` (sessions/messages are nowhere near 2^53). The adapter\n * widens via `Number(row.id)` on every read; writes accept `number` and\n * Prisma narrows internally.\n */\nimport type {\n AiSettings,\n AiSettingsPatch,\n AppendMessageInput,\n ChatMessage,\n ChatMessageRole,\n ChatSession,\n CreateSessionInput,\n ListSessionsOpts,\n PersistencePort\n} from '../../server/ports/types';\n\n// ---------------------------------------------------------------------------\n// Structural Prisma shape — covers exactly what this adapter touches.\n// Avoids a value-level dependency on `@prisma/client` (optional peer dep).\n// ---------------------------------------------------------------------------\n\ntype ChatSessionRow = {\n id: bigint | number;\n userId: bigint | number;\n title: string;\n createdAt: Date;\n updatedAt: Date;\n};\n\ntype ChatMessageRow = {\n id: bigint | number;\n sessionId: bigint | number;\n role: string;\n question: string | null;\n blocks: unknown | null;\n prose: unknown | null;\n errorJson: unknown | null;\n createdAt: Date;\n};\n\ntype AiSettingsRow = {\n id: number;\n toolProvider: string;\n gcpLocation: string;\n chatInterface: string;\n updatedAt: Date | null;\n updatedByUserId: bigint | number | null;\n};\n\ntype ChatSessionDelegate = {\n create(args: {\n data: { userId: number; title: string };\n }): Promise<ChatSessionRow>;\n findFirst(args: {\n where: { id: number; userId: number };\n }): Promise<ChatSessionRow | null>;\n findMany(args: {\n where: { userId: number };\n orderBy: { updatedAt: 'desc' };\n take?: number;\n }): Promise<ChatSessionRow[]>;\n update(args: {\n where: { id: number };\n data: { title?: string; updatedAt?: Date };\n }): Promise<ChatSessionRow>;\n updateMany(args: {\n where: { id: number; userId: number };\n data: { title?: string; updatedAt?: Date };\n }): Promise<{ count: number }>;\n deleteMany(args: {\n where: { id: number; userId: number };\n }): Promise<{ count: number }>;\n};\n\ntype ChatMessageDelegate = {\n create(args: {\n data: {\n sessionId: number;\n role: ChatMessageRole;\n question: string | null;\n blocks: unknown | null;\n prose: unknown | null;\n errorJson: unknown | null;\n };\n }): Promise<ChatMessageRow>;\n findMany(args: {\n where: { sessionId: number };\n orderBy: { createdAt: 'asc' };\n }): Promise<ChatMessageRow[]>;\n};\n\ntype AiSettingsDelegate = {\n findUnique(args: { where: { id: number } }): Promise<AiSettingsRow | null>;\n upsert(args: {\n where: { id: number };\n create: {\n id: number;\n toolProvider?: string;\n gcpLocation?: string;\n chatInterface?: string;\n updatedByUserId: number;\n };\n update: {\n toolProvider?: string;\n gcpLocation?: string;\n chatInterface?: string;\n updatedByUserId: number;\n };\n }): Promise<AiSettingsRow>;\n};\n\nexport type PrismaLike = {\n chatSession: ChatSessionDelegate;\n chatMessage: ChatMessageDelegate;\n aiSettings: AiSettingsDelegate;\n};\n\n// ---------------------------------------------------------------------------\n// Row -> domain mappers\n// ---------------------------------------------------------------------------\n\nconst toSession = (row: ChatSessionRow): ChatSession => ({\n id: Number(row.id),\n userId: Number(row.userId),\n title: row.title,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt\n});\n\nconst toMessage = (row: ChatMessageRow): ChatMessage => ({\n id: Number(row.id),\n sessionId: Number(row.sessionId),\n // Stored as VARCHAR(16) but constrained to ChatMessageRole at write time.\n role: row.role as ChatMessageRole,\n question: row.question,\n blocks: row.blocks,\n prose: row.prose,\n errorJson: row.errorJson,\n createdAt: row.createdAt\n});\n\nconst toAiSettings = (row: AiSettingsRow): AiSettings => ({\n toolProvider: row.toolProvider,\n gcpLocation: row.gcpLocation,\n chatInterface: row.chatInterface,\n updatedAt: row.updatedAt,\n updatedByUserId: row.updatedByUserId === null ? null : Number(row.updatedByUserId)\n});\n\nconst DEFAULT_AI_SETTINGS: AiSettings = {\n toolProvider: 'claude',\n gcpLocation: 'us-east5',\n chatInterface: 'custom',\n updatedAt: null,\n updatedByUserId: null\n};\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\nexport function createPrismaPersistence(prisma: PrismaLike): PersistencePort {\n return {\n // Sessions ---------------------------------------------------------------\n\n async createSession(input: CreateSessionInput): Promise<ChatSession> {\n const row = await prisma.chatSession.create({\n data: { userId: input.userId, title: input.title }\n });\n return toSession(row);\n },\n\n async getSession(id: number, userId: number): Promise<ChatSession | null> {\n const row = await prisma.chatSession.findFirst({ where: { id, userId } });\n return row ? toSession(row) : null;\n },\n\n async listSessionsForUser(\n userId: number,\n opts?: ListSessionsOpts\n ): Promise<ChatSession[]> {\n const rows = await prisma.chatSession.findMany({\n where: { userId },\n orderBy: { updatedAt: 'desc' },\n take: opts?.limit\n });\n return rows.map(toSession);\n },\n\n async updateSession(\n id: number,\n userId: number,\n patch: { title?: string }\n ): Promise<void> {\n // updateMany so the userId scope filters; no-ops cleanly when the\n // session doesn't exist or belongs to another user.\n await prisma.chatSession.updateMany({\n where: { id, userId },\n data: { ...patch, updatedAt: new Date() }\n });\n },\n\n async deleteSession(id: number, userId: number): Promise<void> {\n await prisma.chatSession.deleteMany({ where: { id, userId } });\n },\n\n // Messages ---------------------------------------------------------------\n\n async appendMessage(input: AppendMessageInput): Promise<ChatMessage> {\n const row = await prisma.chatMessage.create({\n data: {\n sessionId: input.sessionId,\n role: input.role,\n question: input.question ?? null,\n blocks: input.blocks ?? null,\n prose: input.prose ?? null,\n errorJson: input.errorJson ?? null\n }\n });\n return toMessage(row);\n },\n\n async listMessagesForSession(\n sessionId: number,\n userId: number\n ): Promise<ChatMessage[]> {\n // Re-assert ownership before returning rows. A caller passing a\n // sessionId they don't own should get an empty list, not someone\n // else's transcript.\n const session = await prisma.chatSession.findFirst({\n where: { id: sessionId, userId }\n });\n if (!session) return [];\n const rows = await prisma.chatMessage.findMany({\n where: { sessionId },\n orderBy: { createdAt: 'asc' }\n });\n return rows.map(toMessage);\n },\n\n // AI settings (singleton row at id=1) -----------------------------------\n\n async getAiSettings(): Promise<AiSettings> {\n const row = await prisma.aiSettings.findUnique({ where: { id: 1 } });\n return row ? toAiSettings(row) : { ...DEFAULT_AI_SETTINGS };\n },\n\n async updateAiSettings(\n patch: AiSettingsPatch,\n byUserId: number\n ): Promise<AiSettings> {\n const row = await prisma.aiSettings.upsert({\n where: { id: 1 },\n create: { id: 1, ...patch, updatedByUserId: byUserId },\n update: { ...patch, updatedByUserId: byUserId }\n });\n return toAiSettings(row);\n }\n };\n}\n","/**\n * The Prisma schema fragment hosts paste into their own `schema.prisma`.\n *\n * Kept in lock-step with `prisma/chat-models.prisma` at the package root.\n * The fragment is inlined here (not read from disk) so consumer bundlers\n * never need to resolve a runtime fs path.\n *\n * Codegen tooling can import this constant and write it out, splice it into\n * a host's `schema.prisma`, etc.\n */\nexport const prismaSchemaFragment: string = `// =============================================================================\n// @firstlovecenter/ai-chat — Prisma schema fragment\n// =============================================================================\n//\n// HOSTS: copy the three models below into your own \\`schema.prisma\\`, then run\n//\n// pnpm prisma generate\n// pnpm prisma migrate dev --name flc_ai_chat\n//\n// Column names, SQL types, and indexes match the Drizzle adapter shipped at\n// \\`@firstlovecenter/ai-chat/server/drizzle\\` byte-for-byte, so the underlying MySQL schema\n// is identical regardless of which ORM your host picks. A host could swap\n// from one adapter to the other without touching data.\n// =============================================================================\n\nmodel ChatSession {\n id BigInt @id @default(autoincrement()) @db.UnsignedBigInt\n userId BigInt @map(\"user_id\") @db.UnsignedBigInt\n title String @db.VarChar(200)\n createdAt DateTime @default(now()) @map(\"created_at\") @db.DateTime(0)\n updatedAt DateTime @default(now()) @map(\"updated_at\") @db.DateTime(0)\n\n @@index([userId, updatedAt], map: \"idx_chat_session_user_updated\")\n @@map(\"chat_sessions\")\n}\n\nmodel ChatMessage {\n id BigInt @id @default(autoincrement()) @db.UnsignedBigInt\n sessionId BigInt @map(\"session_id\") @db.UnsignedBigInt\n role String @db.VarChar(16)\n question String? @db.Text\n blocks Json?\n prose Json?\n errorJson Json? @map(\"error_json\")\n createdAt DateTime @default(now()) @map(\"created_at\") @db.DateTime(0)\n\n @@index([sessionId, createdAt], map: \"idx_chat_msg_session_created\")\n @@map(\"chat_messages\")\n}\n\nmodel AiSettings {\n id Int @id @default(1) @db.UnsignedTinyInt\n toolProvider String @default(\"claude\") @map(\"tool_provider\") @db.VarChar(32)\n gcpLocation String @default(\"us-east5\") @map(\"gcp_location\") @db.VarChar(32)\n chatInterface String @default(\"custom\") @map(\"chat_interface\") @db.VarChar(32)\n updatedAt DateTime @default(now()) @map(\"updated_at\") @db.DateTime(0)\n updatedByUserId BigInt? @map(\"updated_by_user_id\") @db.UnsignedBigInt\n\n @@map(\"ai_settings\")\n}\n`;\n"]}
@@ -0,0 +1,163 @@
1
+ import { m as ChatMessageRole, c as PersistencePort } from '../types-DNwFvL-C.cjs';
2
+ import 'google-auth-library';
3
+
4
+ /**
5
+ * Prisma `PersistencePort` adapter.
6
+ *
7
+ * Hosts call `createPrismaPersistence(prisma)` with their own configured
8
+ * `PrismaClient` and pass the result to `configureAiChat({ persistence, ... })`.
9
+ *
10
+ * The concrete `PrismaClient` is an OPTIONAL peer dep, so this module never
11
+ * imports a runtime value from `@prisma/client`. Instead it accepts a
12
+ * structural duck-typed shape covering only the delegate methods the port
13
+ * uses. Any object that satisfies `PrismaLike` works — including a real
14
+ * `PrismaClient`, a Prisma extension proxy, or a hand-rolled fake in tests.
15
+ *
16
+ * BigInt note: Prisma returns row IDs as JavaScript `BigInt`. Domain types
17
+ * use plain `number` (sessions/messages are nowhere near 2^53). The adapter
18
+ * widens via `Number(row.id)` on every read; writes accept `number` and
19
+ * Prisma narrows internally.
20
+ */
21
+
22
+ type ChatSessionRow = {
23
+ id: bigint | number;
24
+ userId: bigint | number;
25
+ title: string;
26
+ createdAt: Date;
27
+ updatedAt: Date;
28
+ };
29
+ type ChatMessageRow = {
30
+ id: bigint | number;
31
+ sessionId: bigint | number;
32
+ role: string;
33
+ question: string | null;
34
+ blocks: unknown | null;
35
+ prose: unknown | null;
36
+ errorJson: unknown | null;
37
+ createdAt: Date;
38
+ };
39
+ type AiSettingsRow = {
40
+ id: number;
41
+ toolProvider: string;
42
+ gcpLocation: string;
43
+ chatInterface: string;
44
+ updatedAt: Date | null;
45
+ updatedByUserId: bigint | number | null;
46
+ };
47
+ type ChatSessionDelegate = {
48
+ create(args: {
49
+ data: {
50
+ userId: number;
51
+ title: string;
52
+ };
53
+ }): Promise<ChatSessionRow>;
54
+ findFirst(args: {
55
+ where: {
56
+ id: number;
57
+ userId: number;
58
+ };
59
+ }): Promise<ChatSessionRow | null>;
60
+ findMany(args: {
61
+ where: {
62
+ userId: number;
63
+ };
64
+ orderBy: {
65
+ updatedAt: 'desc';
66
+ };
67
+ take?: number;
68
+ }): Promise<ChatSessionRow[]>;
69
+ update(args: {
70
+ where: {
71
+ id: number;
72
+ };
73
+ data: {
74
+ title?: string;
75
+ updatedAt?: Date;
76
+ };
77
+ }): Promise<ChatSessionRow>;
78
+ updateMany(args: {
79
+ where: {
80
+ id: number;
81
+ userId: number;
82
+ };
83
+ data: {
84
+ title?: string;
85
+ updatedAt?: Date;
86
+ };
87
+ }): Promise<{
88
+ count: number;
89
+ }>;
90
+ deleteMany(args: {
91
+ where: {
92
+ id: number;
93
+ userId: number;
94
+ };
95
+ }): Promise<{
96
+ count: number;
97
+ }>;
98
+ };
99
+ type ChatMessageDelegate = {
100
+ create(args: {
101
+ data: {
102
+ sessionId: number;
103
+ role: ChatMessageRole;
104
+ question: string | null;
105
+ blocks: unknown | null;
106
+ prose: unknown | null;
107
+ errorJson: unknown | null;
108
+ };
109
+ }): Promise<ChatMessageRow>;
110
+ findMany(args: {
111
+ where: {
112
+ sessionId: number;
113
+ };
114
+ orderBy: {
115
+ createdAt: 'asc';
116
+ };
117
+ }): Promise<ChatMessageRow[]>;
118
+ };
119
+ type AiSettingsDelegate = {
120
+ findUnique(args: {
121
+ where: {
122
+ id: number;
123
+ };
124
+ }): Promise<AiSettingsRow | null>;
125
+ upsert(args: {
126
+ where: {
127
+ id: number;
128
+ };
129
+ create: {
130
+ id: number;
131
+ toolProvider?: string;
132
+ gcpLocation?: string;
133
+ chatInterface?: string;
134
+ updatedByUserId: number;
135
+ };
136
+ update: {
137
+ toolProvider?: string;
138
+ gcpLocation?: string;
139
+ chatInterface?: string;
140
+ updatedByUserId: number;
141
+ };
142
+ }): Promise<AiSettingsRow>;
143
+ };
144
+ type PrismaLike = {
145
+ chatSession: ChatSessionDelegate;
146
+ chatMessage: ChatMessageDelegate;
147
+ aiSettings: AiSettingsDelegate;
148
+ };
149
+ declare function createPrismaPersistence(prisma: PrismaLike): PersistencePort;
150
+
151
+ /**
152
+ * The Prisma schema fragment hosts paste into their own `schema.prisma`.
153
+ *
154
+ * Kept in lock-step with `prisma/chat-models.prisma` at the package root.
155
+ * The fragment is inlined here (not read from disk) so consumer bundlers
156
+ * never need to resolve a runtime fs path.
157
+ *
158
+ * Codegen tooling can import this constant and write it out, splice it into
159
+ * a host's `schema.prisma`, etc.
160
+ */
161
+ declare const prismaSchemaFragment: string;
162
+
163
+ export { type PrismaLike, createPrismaPersistence, prismaSchemaFragment };
@@ -0,0 +1,163 @@
1
+ import { m as ChatMessageRole, c as PersistencePort } from '../types-DNwFvL-C.js';
2
+ import 'google-auth-library';
3
+
4
+ /**
5
+ * Prisma `PersistencePort` adapter.
6
+ *
7
+ * Hosts call `createPrismaPersistence(prisma)` with their own configured
8
+ * `PrismaClient` and pass the result to `configureAiChat({ persistence, ... })`.
9
+ *
10
+ * The concrete `PrismaClient` is an OPTIONAL peer dep, so this module never
11
+ * imports a runtime value from `@prisma/client`. Instead it accepts a
12
+ * structural duck-typed shape covering only the delegate methods the port
13
+ * uses. Any object that satisfies `PrismaLike` works — including a real
14
+ * `PrismaClient`, a Prisma extension proxy, or a hand-rolled fake in tests.
15
+ *
16
+ * BigInt note: Prisma returns row IDs as JavaScript `BigInt`. Domain types
17
+ * use plain `number` (sessions/messages are nowhere near 2^53). The adapter
18
+ * widens via `Number(row.id)` on every read; writes accept `number` and
19
+ * Prisma narrows internally.
20
+ */
21
+
22
+ type ChatSessionRow = {
23
+ id: bigint | number;
24
+ userId: bigint | number;
25
+ title: string;
26
+ createdAt: Date;
27
+ updatedAt: Date;
28
+ };
29
+ type ChatMessageRow = {
30
+ id: bigint | number;
31
+ sessionId: bigint | number;
32
+ role: string;
33
+ question: string | null;
34
+ blocks: unknown | null;
35
+ prose: unknown | null;
36
+ errorJson: unknown | null;
37
+ createdAt: Date;
38
+ };
39
+ type AiSettingsRow = {
40
+ id: number;
41
+ toolProvider: string;
42
+ gcpLocation: string;
43
+ chatInterface: string;
44
+ updatedAt: Date | null;
45
+ updatedByUserId: bigint | number | null;
46
+ };
47
+ type ChatSessionDelegate = {
48
+ create(args: {
49
+ data: {
50
+ userId: number;
51
+ title: string;
52
+ };
53
+ }): Promise<ChatSessionRow>;
54
+ findFirst(args: {
55
+ where: {
56
+ id: number;
57
+ userId: number;
58
+ };
59
+ }): Promise<ChatSessionRow | null>;
60
+ findMany(args: {
61
+ where: {
62
+ userId: number;
63
+ };
64
+ orderBy: {
65
+ updatedAt: 'desc';
66
+ };
67
+ take?: number;
68
+ }): Promise<ChatSessionRow[]>;
69
+ update(args: {
70
+ where: {
71
+ id: number;
72
+ };
73
+ data: {
74
+ title?: string;
75
+ updatedAt?: Date;
76
+ };
77
+ }): Promise<ChatSessionRow>;
78
+ updateMany(args: {
79
+ where: {
80
+ id: number;
81
+ userId: number;
82
+ };
83
+ data: {
84
+ title?: string;
85
+ updatedAt?: Date;
86
+ };
87
+ }): Promise<{
88
+ count: number;
89
+ }>;
90
+ deleteMany(args: {
91
+ where: {
92
+ id: number;
93
+ userId: number;
94
+ };
95
+ }): Promise<{
96
+ count: number;
97
+ }>;
98
+ };
99
+ type ChatMessageDelegate = {
100
+ create(args: {
101
+ data: {
102
+ sessionId: number;
103
+ role: ChatMessageRole;
104
+ question: string | null;
105
+ blocks: unknown | null;
106
+ prose: unknown | null;
107
+ errorJson: unknown | null;
108
+ };
109
+ }): Promise<ChatMessageRow>;
110
+ findMany(args: {
111
+ where: {
112
+ sessionId: number;
113
+ };
114
+ orderBy: {
115
+ createdAt: 'asc';
116
+ };
117
+ }): Promise<ChatMessageRow[]>;
118
+ };
119
+ type AiSettingsDelegate = {
120
+ findUnique(args: {
121
+ where: {
122
+ id: number;
123
+ };
124
+ }): Promise<AiSettingsRow | null>;
125
+ upsert(args: {
126
+ where: {
127
+ id: number;
128
+ };
129
+ create: {
130
+ id: number;
131
+ toolProvider?: string;
132
+ gcpLocation?: string;
133
+ chatInterface?: string;
134
+ updatedByUserId: number;
135
+ };
136
+ update: {
137
+ toolProvider?: string;
138
+ gcpLocation?: string;
139
+ chatInterface?: string;
140
+ updatedByUserId: number;
141
+ };
142
+ }): Promise<AiSettingsRow>;
143
+ };
144
+ type PrismaLike = {
145
+ chatSession: ChatSessionDelegate;
146
+ chatMessage: ChatMessageDelegate;
147
+ aiSettings: AiSettingsDelegate;
148
+ };
149
+ declare function createPrismaPersistence(prisma: PrismaLike): PersistencePort;
150
+
151
+ /**
152
+ * The Prisma schema fragment hosts paste into their own `schema.prisma`.
153
+ *
154
+ * Kept in lock-step with `prisma/chat-models.prisma` at the package root.
155
+ * The fragment is inlined here (not read from disk) so consumer bundlers
156
+ * never need to resolve a runtime fs path.
157
+ *
158
+ * Codegen tooling can import this constant and write it out, splice it into
159
+ * a host's `schema.prisma`, etc.
160
+ */
161
+ declare const prismaSchemaFragment: string;
162
+
163
+ export { type PrismaLike, createPrismaPersistence, prismaSchemaFragment };
@@ -0,0 +1,160 @@
1
+ // src/adapters/prisma/adapter.ts
2
+ var toSession = (row) => ({
3
+ id: Number(row.id),
4
+ userId: Number(row.userId),
5
+ title: row.title,
6
+ createdAt: row.createdAt,
7
+ updatedAt: row.updatedAt
8
+ });
9
+ var toMessage = (row) => ({
10
+ id: Number(row.id),
11
+ sessionId: Number(row.sessionId),
12
+ // Stored as VARCHAR(16) but constrained to ChatMessageRole at write time.
13
+ role: row.role,
14
+ question: row.question,
15
+ blocks: row.blocks,
16
+ prose: row.prose,
17
+ errorJson: row.errorJson,
18
+ createdAt: row.createdAt
19
+ });
20
+ var toAiSettings = (row) => ({
21
+ toolProvider: row.toolProvider,
22
+ gcpLocation: row.gcpLocation,
23
+ chatInterface: row.chatInterface,
24
+ updatedAt: row.updatedAt,
25
+ updatedByUserId: row.updatedByUserId === null ? null : Number(row.updatedByUserId)
26
+ });
27
+ var DEFAULT_AI_SETTINGS = {
28
+ toolProvider: "claude",
29
+ gcpLocation: "us-east5",
30
+ chatInterface: "custom",
31
+ updatedAt: null,
32
+ updatedByUserId: null
33
+ };
34
+ function createPrismaPersistence(prisma) {
35
+ return {
36
+ // Sessions ---------------------------------------------------------------
37
+ async createSession(input) {
38
+ const row = await prisma.chatSession.create({
39
+ data: { userId: input.userId, title: input.title }
40
+ });
41
+ return toSession(row);
42
+ },
43
+ async getSession(id, userId) {
44
+ const row = await prisma.chatSession.findFirst({ where: { id, userId } });
45
+ return row ? toSession(row) : null;
46
+ },
47
+ async listSessionsForUser(userId, opts) {
48
+ const rows = await prisma.chatSession.findMany({
49
+ where: { userId },
50
+ orderBy: { updatedAt: "desc" },
51
+ take: opts?.limit
52
+ });
53
+ return rows.map(toSession);
54
+ },
55
+ async updateSession(id, userId, patch) {
56
+ await prisma.chatSession.updateMany({
57
+ where: { id, userId },
58
+ data: { ...patch, updatedAt: /* @__PURE__ */ new Date() }
59
+ });
60
+ },
61
+ async deleteSession(id, userId) {
62
+ await prisma.chatSession.deleteMany({ where: { id, userId } });
63
+ },
64
+ // Messages ---------------------------------------------------------------
65
+ async appendMessage(input) {
66
+ const row = await prisma.chatMessage.create({
67
+ data: {
68
+ sessionId: input.sessionId,
69
+ role: input.role,
70
+ question: input.question ?? null,
71
+ blocks: input.blocks ?? null,
72
+ prose: input.prose ?? null,
73
+ errorJson: input.errorJson ?? null
74
+ }
75
+ });
76
+ return toMessage(row);
77
+ },
78
+ async listMessagesForSession(sessionId, userId) {
79
+ const session = await prisma.chatSession.findFirst({
80
+ where: { id: sessionId, userId }
81
+ });
82
+ if (!session) return [];
83
+ const rows = await prisma.chatMessage.findMany({
84
+ where: { sessionId },
85
+ orderBy: { createdAt: "asc" }
86
+ });
87
+ return rows.map(toMessage);
88
+ },
89
+ // AI settings (singleton row at id=1) -----------------------------------
90
+ async getAiSettings() {
91
+ const row = await prisma.aiSettings.findUnique({ where: { id: 1 } });
92
+ return row ? toAiSettings(row) : { ...DEFAULT_AI_SETTINGS };
93
+ },
94
+ async updateAiSettings(patch, byUserId) {
95
+ const row = await prisma.aiSettings.upsert({
96
+ where: { id: 1 },
97
+ create: { id: 1, ...patch, updatedByUserId: byUserId },
98
+ update: { ...patch, updatedByUserId: byUserId }
99
+ });
100
+ return toAiSettings(row);
101
+ }
102
+ };
103
+ }
104
+
105
+ // src/adapters/prisma/schema.ts
106
+ var prismaSchemaFragment = `// =============================================================================
107
+ // @firstlovecenter/ai-chat \u2014 Prisma schema fragment
108
+ // =============================================================================
109
+ //
110
+ // HOSTS: copy the three models below into your own \`schema.prisma\`, then run
111
+ //
112
+ // pnpm prisma generate
113
+ // pnpm prisma migrate dev --name flc_ai_chat
114
+ //
115
+ // Column names, SQL types, and indexes match the Drizzle adapter shipped at
116
+ // \`@firstlovecenter/ai-chat/server/drizzle\` byte-for-byte, so the underlying MySQL schema
117
+ // is identical regardless of which ORM your host picks. A host could swap
118
+ // from one adapter to the other without touching data.
119
+ // =============================================================================
120
+
121
+ model ChatSession {
122
+ id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
123
+ userId BigInt @map("user_id") @db.UnsignedBigInt
124
+ title String @db.VarChar(200)
125
+ createdAt DateTime @default(now()) @map("created_at") @db.DateTime(0)
126
+ updatedAt DateTime @default(now()) @map("updated_at") @db.DateTime(0)
127
+
128
+ @@index([userId, updatedAt], map: "idx_chat_session_user_updated")
129
+ @@map("chat_sessions")
130
+ }
131
+
132
+ model ChatMessage {
133
+ id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
134
+ sessionId BigInt @map("session_id") @db.UnsignedBigInt
135
+ role String @db.VarChar(16)
136
+ question String? @db.Text
137
+ blocks Json?
138
+ prose Json?
139
+ errorJson Json? @map("error_json")
140
+ createdAt DateTime @default(now()) @map("created_at") @db.DateTime(0)
141
+
142
+ @@index([sessionId, createdAt], map: "idx_chat_msg_session_created")
143
+ @@map("chat_messages")
144
+ }
145
+
146
+ model AiSettings {
147
+ id Int @id @default(1) @db.UnsignedTinyInt
148
+ toolProvider String @default("claude") @map("tool_provider") @db.VarChar(32)
149
+ gcpLocation String @default("us-east5") @map("gcp_location") @db.VarChar(32)
150
+ chatInterface String @default("custom") @map("chat_interface") @db.VarChar(32)
151
+ updatedAt DateTime @default(now()) @map("updated_at") @db.DateTime(0)
152
+ updatedByUserId BigInt? @map("updated_by_user_id") @db.UnsignedBigInt
153
+
154
+ @@map("ai_settings")
155
+ }
156
+ `;
157
+
158
+ export { createPrismaPersistence, prismaSchemaFragment };
159
+ //# sourceMappingURL=index.js.map
160
+ //# sourceMappingURL=index.js.map