@ykurilov/agent-chat-messengers 0.56.0 → 0.58.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @ykurilov/agent-chat-messengers
2
2
 
3
+ ## 0.58.0
4
+
5
+ ### Patch Changes
6
+
7
+ - 760962e: Хранилища пакетов на PostgreSQL можно открывать одновременно на совсем пустой базе. Раньше пакеты разом создавали
8
+ схему `agent_chat`, проигравший падал на `CREATE SCHEMA IF NOT EXISTS`, и его хранилище не работало до перезапуска.
9
+ Теперь схема создаётся под общей блокировкой всех пакетов, она держится только на создании схемы. Миграции идут под
10
+ своей блокировкой пакета: разные пакеты друг друга не ждут, взаимной блокировки нет. Столкновение «такое уже есть»
11
+ повторяется само. Новое в core: `migratePostgresSchema({ connect, lockKey, migrate })` — для своих пакетов на той же
12
+ схеме.
13
+
14
+ ### Как обновить приложение
15
+
16
+ Ничего делать не нужно.
17
+ - Updated dependencies [332d90e]
18
+ - Updated dependencies [2ed600d]
19
+ - Updated dependencies [760962e]
20
+ - @ykurilov/agent-chat-core@0.58.0
21
+ - @ykurilov/agent-chat-react@0.58.0
22
+
23
+ ## 0.57.0
24
+
25
+ ### Patch Changes
26
+
27
+ - Updated dependencies [ade99cc]
28
+ - Updated dependencies [86afa20]
29
+ - Updated dependencies [624c10c]
30
+ - @ykurilov/agent-chat-core@0.57.0
31
+ - @ykurilov/agent-chat-react@0.57.0
32
+
3
33
  ## 0.56.0
4
34
 
5
35
  ### Patch Changes
package/dist/postgres.js CHANGED
@@ -11,6 +11,7 @@ import pg from "pg";
11
11
  import { and, asc, count, eq, gt, gte, inArray, isNull, lt, lte, or, sql } from "drizzle-orm";
12
12
  import { drizzle } from "drizzle-orm/node-postgres";
13
13
  import { migrate } from "drizzle-orm/node-postgres/migrator";
14
+ import { migratePostgresSchema } from "@ykurilov/agent-chat-core";
14
15
 
15
16
  // src/store/postgres-schema.ts
16
17
  import { bigserial, boolean, index, integer, jsonb, pgSchema, primaryKey, text, timestamp } from "drizzle-orm/pg-core";
@@ -136,17 +137,11 @@ function withoutNul(value) {
136
137
  return value;
137
138
  }
138
139
  async function migrateMessengersToLatest(pool, migrationsFolder2) {
139
- const client = await pool.connect();
140
- try {
141
- await client.query("select pg_advisory_lock($1)", [MESSENGERS_MIGRATION_LOCK_KEY]);
142
- try {
143
- await migrate(drizzle(client), { migrationsFolder: migrationsFolder2, migrationsSchema: "agent_chat", migrationsTable: MESSENGERS_MIGRATIONS_TABLE });
144
- } finally {
145
- await client.query("select pg_advisory_unlock($1)", [MESSENGERS_MIGRATION_LOCK_KEY]);
146
- }
147
- } finally {
148
- client.release();
149
- }
140
+ await migratePostgresSchema({
141
+ connect: () => pool.connect(),
142
+ lockKey: MESSENGERS_MIGRATION_LOCK_KEY,
143
+ migrate: (client) => migrate(drizzle(client), { migrationsFolder: migrationsFolder2, migrationsSchema: "agent_chat", migrationsTable: MESSENGERS_MIGRATIONS_TABLE })
144
+ });
150
145
  }
151
146
  function createPostgresMessengerStore(pool) {
152
147
  const db = drizzle(pool);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/postgres.ts","../src/store/postgres-store.ts","../src/store/postgres-schema.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport pg from \"pg\";\nimport { createPostgresMessengerStore as openPostgresMessengerStore, migrateMessengersToLatest } from \"./store/postgres-store\";\nimport type { ClosableMessengerStore } from \"./types\";\n\nexport { MESSENGERS_MIGRATIONS_TABLE, MESSENGERS_MIGRATION_LOCK_KEY } from \"./store/migration-settings\";\nexport * from \"./store/postgres-schema\";\nexport type { ClosableMessengerStore, MessengerStore } from \"./types\";\n\nconst DEFAULT_POOL_SIZE = 5;\nconst migrationsFolder = fileURLToPath(new URL(\"../drizzle/postgres\", import.meta.url));\n\nexport interface PostgresMessengerStoreOptions {\n /** Например `postgres://user:password@host:5432/database`; та же база, что у `createPostgresChatStorage`. */\n connectionString: string;\n /** Сколько соединений держать открытыми. По умолчанию 5. */\n poolSize?: number;\n logError?(message: string): void;\n}\n\n/**\n * Хранилище мессенджеров в PostgreSQL. Таблицы — в схеме `agent_chat`; схема приводится к последней миграции под блокировкой базы.\n * Пул соединений хранилище открывает само; `close()` его закрывает (зовите при остановке сервера после `messengers.stop()`).\n * Не удалось подготовить хранилище — пул закрывается до ошибки.\n */\nexport async function createPostgresMessengerStore(options: PostgresMessengerStoreOptions): Promise<ClosableMessengerStore> {\n const logError = options.logError ?? ((message: string) => console.error(message));\n const pool = new pg.Pool({ connectionString: options.connectionString, max: options.poolSize ?? DEFAULT_POOL_SIZE });\n // Без обработчика оборвавшееся соединение простаивающего пула роняет процесс.\n pool.on(\"error\", (error) => logError(`мессенджеры: соединение с PostgreSQL оборвалось: ${error.message}`));\n try {\n await migrateMessengersToLatest(pool, migrationsFolder);\n } catch (error) {\n await pool.end().catch(() => {});\n const reason = error instanceof Error ? error.message : String(error);\n throw new Error(`Не удалось подготовить хранилище мессенджеров в PostgreSQL: ${reason.split(options.connectionString).join(\"<строка подключения>\")}`);\n }\n return openPostgresMessengerStore(pool);\n}\n","import { and, asc, count, eq, gt, gte, inArray, isNull, lt, lte, or, sql } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/node-postgres\";\nimport { migrate } from \"drizzle-orm/node-postgres/migrator\";\nimport type { Pool } from \"pg\";\nimport type { ButtonAction, ClosableMessengerStore, ConversationKey, MessengerConversation, MessengerLink, PendingDelivery } from \"../types\";\nimport { MESSENGERS_MIGRATION_LOCK_KEY, MESSENGERS_MIGRATIONS_TABLE } from \"./migration-settings\";\nimport { messengerButtons, messengerConversations, messengerDeliveries, messengerLeases, messengerLinkCodes, messengerLinks, messengerMemberHits, messengerUpdates } from \"./postgres-schema\";\n\nconst date = (iso: string) => new Date(iso);\n\n/**\n * Postgres не хранит символ NUL ни в тексте, ни в jsonb: запись с ним падает. Строки от мессенджера и модели (имя,\n * содержимое обновления, текст итога, действие кнопки) пишутся без него.\n */\nfunction withoutNul<T>(value: T): T {\n if (typeof value === \"string\") return value.replaceAll(\"\\u0000\", \"\") as T;\n if (Array.isArray(value)) return value.map(withoutNul) as T;\n if (value !== null && typeof value === \"object\" && Object.getPrototypeOf(value) === Object.prototype) {\n return Object.fromEntries(Object.entries(value).map(([field, item]) => [withoutNul(field), withoutNul(item)])) as T;\n }\n return value;\n}\n\n/**\n * Приводит таблицы мессенджеров к последней миграции. Идёт на одном соединении под блокировкой базы:\n * если несколько серверов стартуют сразу, второй ждёт первого и видит применённые миграции.\n */\nexport async function migrateMessengersToLatest(pool: Pool, migrationsFolder: string): Promise<void> {\n const client = await pool.connect();\n try {\n await client.query(\"select pg_advisory_lock($1)\", [MESSENGERS_MIGRATION_LOCK_KEY]);\n try {\n await migrate(drizzle(client), { migrationsFolder, migrationsSchema: \"agent_chat\", migrationsTable: MESSENGERS_MIGRATIONS_TABLE });\n } finally {\n await client.query(\"select pg_advisory_unlock($1)\", [MESSENGERS_MIGRATION_LOCK_KEY]);\n }\n } finally {\n client.release();\n }\n}\n\n/** Хранилище мессенджеров в PostgreSQL (схема `agent_chat`). Одноразовое берётся одним условным запросом. Закрытие закрывает пул, повторное — ничего не делает. */\nexport function createPostgresMessengerStore(pool: Pool): ClosableMessengerStore {\n const db = drizzle(pool);\n let closing: Promise<void> | undefined;\n\n const conversationIs = (key: ConversationKey) =>\n and(eq(messengerConversations.messenger, key.messenger), eq(messengerConversations.botId, key.botId), eq(messengerConversations.externalChatId, key.externalChatId));\n\n return {\n async saveLinkCode(code) {\n await db.insert(messengerLinkCodes).values({ codeHash: code.codeHash, userId: code.userId, messenger: code.messenger, expiresAt: date(code.expiresAt) });\n },\n async consumeLinkCode(codeHash, messenger, now) {\n const [row] = await db\n .update(messengerLinkCodes)\n .set({ usedAt: date(now) })\n .where(\n and(\n eq(messengerLinkCodes.codeHash, codeHash),\n eq(messengerLinkCodes.messenger, messenger),\n isNull(messengerLinkCodes.usedAt),\n gt(messengerLinkCodes.expiresAt, date(now)),\n ),\n )\n .returning({ userId: messengerLinkCodes.userId });\n return row ?? null;\n },\n async saveLink(link) {\n const values = withoutNul({ ...link, username: link.username ?? null, linkedAt: date(link.linkedAt) });\n await db\n .insert(messengerLinks)\n .values(values)\n .onConflictDoUpdate({ target: [messengerLinks.messenger, messengerLinks.botId, messengerLinks.externalUserId], set: values });\n },\n async findLink(messenger, botId, externalUserId) {\n const [row] = await db\n .select()\n .from(messengerLinks)\n .where(and(eq(messengerLinks.messenger, messenger), eq(messengerLinks.botId, botId), eq(messengerLinks.externalUserId, externalUserId)));\n return row ? toLink(row) : null;\n },\n async listLinks(userId) {\n return (await db.select().from(messengerLinks).where(eq(messengerLinks.userId, userId)).orderBy(asc(messengerLinks.linkedAt))).map(toLink);\n },\n async removeLinks(userId, messenger) {\n await db.delete(messengerLinks).where(and(eq(messengerLinks.userId, userId), eq(messengerLinks.messenger, messenger)));\n },\n async getConversation(key) {\n const [row] = await db.select().from(messengerConversations).where(conversationIs(key));\n return row ? toConversation(row) : null;\n },\n async saveConversation(conversation) {\n const values = {\n ...conversation,\n connectedBy: conversation.connectedBy ?? null,\n currentChatId: conversation.currentChatId ?? null,\n createdAt: date(conversation.createdAt),\n updatedAt: date(conversation.updatedAt),\n };\n await db\n .insert(messengerConversations)\n .values(values)\n .onConflictDoUpdate({ target: [messengerConversations.messenger, messengerConversations.botId, messengerConversations.externalChatId], set: values });\n },\n async setCurrentChat(key, chatId, at) {\n await db.update(messengerConversations).set({ currentChatId: chatId, updatedAt: date(at) }).where(conversationIs(key));\n },\n async setBlocked(key, blocked, at) {\n await db.update(messengerConversations).set({ blocked, updatedAt: date(at) }).where(conversationIs(key));\n },\n async setActive(key, active, at) {\n await db.update(messengerConversations).set({ active, updatedAt: date(at) }).where(conversationIs(key));\n },\n async listConversationsOfChat(chatId) {\n return (await db.select().from(messengerConversations).where(eq(messengerConversations.currentChatId, chatId))).map(toConversation);\n },\n async listConversationsOfUser(userId) {\n return (await db.select().from(messengerConversations).where(eq(messengerConversations.userId, userId))).map(toConversation);\n },\n async addMemberHit(hit) {\n await db\n .insert(messengerMemberHits)\n .values({ ...hit, at: date(hit.at) })\n .onConflictDoNothing();\n },\n async countMemberHits(key, externalUserId, since) {\n const [row] = await db\n .select({ value: count() })\n .from(messengerMemberHits)\n .where(\n and(\n eq(messengerMemberHits.messenger, key.messenger),\n eq(messengerMemberHits.botId, key.botId),\n eq(messengerMemberHits.externalChatId, key.externalChatId),\n eq(messengerMemberHits.externalUserId, externalUserId),\n gte(messengerMemberHits.at, date(since)),\n ),\n );\n return Number(row?.value ?? 0);\n },\n async claimUpdate(key, now, work) {\n const content = work && { messenger: work.messenger, botId: work.botId, payload: withoutNul(work.update), holder: work.holder, leaseUntil: date(work.leaseUntil) };\n const rows = await db\n .insert(messengerUpdates)\n .values({ key, status: \"received\", receivedAt: date(now), ...content })\n .onConflictDoNothing()\n .returning({ key: messengerUpdates.key });\n return rows.length === 1;\n },\n async finishUpdate(key, status, now) {\n await db.update(messengerUpdates).set({ status, finishedAt: date(now), payload: null, holder: null, leaseUntil: null }).where(eq(messengerUpdates.key, key));\n },\n async renewUpdates(keys, holder, leaseUntil) {\n if (keys.length === 0) return;\n await db\n .update(messengerUpdates)\n .set({ leaseUntil: date(leaseUntil) })\n .where(and(inArray(messengerUpdates.key, [...keys]), eq(messengerUpdates.holder, holder), eq(messengerUpdates.status, \"received\")));\n },\n async releaseUpdate(key, holder, retryAt) {\n await db\n .update(messengerUpdates)\n .set({ holder: \"\", leaseUntil: date(retryAt) })\n .where(and(eq(messengerUpdates.key, key), eq(messengerUpdates.holder, holder), eq(messengerUpdates.status, \"received\")));\n },\n async takeAbandonedUpdates(bot, holder, now, leaseUntil, limit) {\n const abandoned = and(\n eq(messengerUpdates.messenger, bot.messenger),\n eq(messengerUpdates.botId, bot.botId),\n eq(messengerUpdates.status, \"received\"),\n lte(messengerUpdates.leaseUntil, date(now)),\n );\n // Как у итогов: занятые другой копией строки пропускаются, выбор — отдельным шагом (WITH).\n const oldest = db\n .$with(\"oldest_updates\")\n .as(db.select({ key: messengerUpdates.key }).from(messengerUpdates).where(abandoned).orderBy(asc(messengerUpdates.receivedAt), asc(messengerUpdates.key)).limit(limit).for(\"update\", { skipLocked: true }));\n const rows = await db\n .with(oldest)\n .update(messengerUpdates)\n .set({ holder, leaseUntil: date(leaseUntil), attempts: sql`${messengerUpdates.attempts} + 1` })\n .where(inArray(messengerUpdates.key, db.select({ key: oldest.key }).from(oldest)))\n .returning({ key: messengerUpdates.key, update: messengerUpdates.payload, receivedAt: messengerUpdates.receivedAt, attempts: messengerUpdates.attempts });\n return rows\n .sort((left, right) => left.receivedAt.getTime() - right.receivedAt.getTime() || (left.key < right.key ? -1 : 1))\n .map(({ key, update, receivedAt, attempts }) => ({ key, update, receivedAt: receivedAt.toISOString(), attempts }));\n },\n async saveButton(button) {\n await db\n .insert(messengerButtons)\n .values({ id: button.id, messenger: button.messenger, botId: button.botId, externalChatId: button.externalChatId, action: withoutNul(button.action), createdAt: date(button.createdAt) });\n },\n async takeButton(key, id, now) {\n const [row] = await db\n .update(messengerButtons)\n .set({ usedAt: date(now) })\n .where(\n and(\n eq(messengerButtons.id, id),\n eq(messengerButtons.messenger, key.messenger),\n eq(messengerButtons.botId, key.botId),\n eq(messengerButtons.externalChatId, key.externalChatId),\n isNull(messengerButtons.usedAt),\n ),\n )\n .returning({ action: messengerButtons.action });\n return row ? (row.action as ButtonAction) : null;\n },\n async savePendingDelivery(delivery, holder, leaseUntil) {\n const { id, messenger, botId, externalChatId, createdAt, ...payload } = delivery;\n const values = { id, messenger, botId, externalChatId, payload: withoutNul(payload), holder, leaseUntil: date(leaseUntil), createdAt: date(createdAt) };\n await db.insert(messengerDeliveries).values(values).onConflictDoUpdate({ target: messengerDeliveries.id, set: values });\n },\n async deletePendingDelivery(id) {\n await db.delete(messengerDeliveries).where(eq(messengerDeliveries.id, id));\n },\n async renewPendingDeliveries(holder, leaseUntil) {\n await db.update(messengerDeliveries).set({ leaseUntil: date(leaseUntil) }).where(eq(messengerDeliveries.holder, holder));\n },\n async takeAbandonedDeliveries(bot, holder, now, leaseUntil, limit) {\n const abandoned = and(eq(messengerDeliveries.messenger, bot.messenger), eq(messengerDeliveries.botId, bot.botId), lte(messengerDeliveries.leaseUntil, date(now)));\n // Строки, которые уже забирает другая копия, пропускаются: каждая достаётся одной. Выбор — отдельным шагом (WITH),\n // иначе PostgreSQL может пересчитать LIMIT по ходу обновления.\n const oldest = db\n .$with(\"oldest\")\n .as(db.select({ id: messengerDeliveries.id }).from(messengerDeliveries).where(abandoned).orderBy(asc(messengerDeliveries.seq)).limit(limit).for(\"update\", { skipLocked: true }));\n const rows = await db\n .with(oldest)\n .update(messengerDeliveries)\n .set({ holder, leaseUntil: date(leaseUntil) })\n .where(inArray(messengerDeliveries.id, db.select({ id: oldest.id }).from(oldest)))\n .returning();\n return rows.sort((left, right) => left.seq - right.seq).map(toDelivery);\n },\n async acquireLease(name, holder, leaseUntil, now) {\n const rows = await db\n .insert(messengerLeases)\n .values({ name, holder, leaseUntil: date(leaseUntil) })\n .onConflictDoUpdate({\n target: messengerLeases.name,\n set: { holder, leaseUntil: date(leaseUntil) },\n setWhere: or(eq(messengerLeases.holder, holder), lte(messengerLeases.leaseUntil, date(now))),\n })\n .returning({ name: messengerLeases.name });\n return rows.length === 1;\n },\n async releaseLease(name, holder) {\n await db.delete(messengerLeases).where(and(eq(messengerLeases.name, name), eq(messengerLeases.holder, holder)));\n },\n async purgeBefore(before) {\n const cutoff = date(before);\n await db.transaction(async (tx) => {\n await tx.delete(messengerLinkCodes).where(lt(messengerLinkCodes.expiresAt, cutoff));\n // Обновление с живой арендой не убирается: его ещё подберут или бросят. Аренда, истёкшая раньше срока хранения, —\n // забытое обновление (например, бот убран из настроек): его уже никто не подберёт.\n await tx.delete(messengerUpdates).where(and(lt(messengerUpdates.receivedAt, cutoff), or(isNull(messengerUpdates.holder), lt(messengerUpdates.leaseUntil, cutoff))));\n await tx.delete(messengerButtons).where(lt(messengerButtons.createdAt, cutoff));\n await tx.delete(messengerMemberHits).where(lt(messengerMemberHits.at, cutoff));\n });\n },\n close() {\n closing ??= pool.end();\n return closing;\n },\n };\n}\n\nfunction toLink(row: typeof messengerLinks.$inferSelect): MessengerLink {\n const { username, linkedAt, ...rest } = row;\n return { ...rest, ...(username !== null && { username }), linkedAt: linkedAt.toISOString() };\n}\n\nfunction toConversation(row: typeof messengerConversations.$inferSelect): MessengerConversation {\n const { connectedBy, currentChatId, createdAt, updatedAt, ...rest } = row;\n return {\n ...rest,\n ...(connectedBy !== null && { connectedBy }),\n ...(currentChatId !== null && { currentChatId }),\n createdAt: createdAt.toISOString(),\n updatedAt: updatedAt.toISOString(),\n };\n}\n\nfunction toDelivery(row: typeof messengerDeliveries.$inferSelect): PendingDelivery {\n return {\n id: row.id,\n messenger: row.messenger,\n botId: row.botId,\n externalChatId: row.externalChatId,\n ...(row.payload as Omit<PendingDelivery, \"id\" | \"createdAt\" | keyof ConversationKey>),\n createdAt: row.createdAt.toISOString(),\n };\n}\n","import { bigserial, boolean, index, integer, jsonb, pgSchema, primaryKey, text, timestamp } from \"drizzle-orm/pg-core\";\n\n/** Та же схема базы, что у хранилища чатов: у приложения одна база и одна схема `agent_chat`. */\nconst agentChatSchema = pgSchema(\"agent_chat\");\n\nconst moment = (name: string) => timestamp(name, { mode: \"date\", precision: 3, withTimezone: true });\nconst messengerColumn = () => text(\"messenger\", { enum: [\"telegram\", \"vk\", \"max\"] }).notNull();\n\n/** Привязанный аккаунт: человек в мессенджере ↔ пользователь приложения. */\nexport const messengerLinks = agentChatSchema.table(\n \"messenger_links\",\n {\n messenger: messengerColumn(),\n botId: text(\"bot_id\").notNull(),\n externalUserId: text(\"external_user_id\").notNull(),\n userId: text(\"user_id\").notNull(),\n name: text(\"name\").notNull(),\n username: text(\"username\"),\n linkedAt: moment(\"linked_at\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.messenger, table.botId, table.externalUserId] }), index(\"messenger_links_user_idx\").on(table.userId, table.messenger)],\n);\n\n/** Одноразовые коды привязки: хранится только хеш кода. */\nexport const messengerLinkCodes = agentChatSchema.table(\n \"messenger_link_codes\",\n {\n codeHash: text(\"code_hash\").primaryKey(),\n userId: text(\"user_id\").notNull(),\n messenger: messengerColumn(),\n expiresAt: moment(\"expires_at\").notNull(),\n usedAt: moment(\"used_at\"),\n },\n (table) => [index(\"messenger_link_codes_expires_idx\").on(table.expiresAt)],\n);\n\n/** Внешний чат (личный или группа): кому он засчитывается, какой чат агента сейчас ведётся, можно ли писать. */\nexport const messengerConversations = agentChatSchema.table(\n \"messenger_conversations\",\n {\n messenger: messengerColumn(),\n botId: text(\"bot_id\").notNull(),\n externalChatId: text(\"external_chat_id\").notNull(),\n type: text(\"type\", { enum: [\"private\", \"group\"] }).notNull(),\n /** Личный чат — привязанный пользователь; группа — плательщик. */\n userId: text(\"user_id\").notNull(),\n /** Группа: кто подключил бота. */\n connectedBy: text(\"connected_by\"),\n currentChatId: text(\"current_chat_id\"),\n /** Человек заблокировал бота: писать ему нельзя. */\n blocked: boolean(\"blocked\").notNull().default(false),\n /** `false` — бота убрали из группы. */\n active: boolean(\"active\").notNull().default(true),\n createdAt: moment(\"created_at\").notNull(),\n updatedAt: moment(\"updated_at\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.messenger, table.botId, table.externalChatId] }), index(\"messenger_conversations_user_idx\").on(table.userId), index(\"messenger_conversations_current_chat_idx\").on(table.currentChatId)],\n);\n\n/** Обращения участника группы к боту: по ним считается предел за час. */\nexport const messengerMemberHits = agentChatSchema.table(\n \"messenger_member_hits\",\n {\n id: text(\"id\").primaryKey(),\n messenger: messengerColumn(),\n botId: text(\"bot_id\").notNull(),\n externalChatId: text(\"external_chat_id\").notNull(),\n externalUserId: text(\"external_user_id\").notNull(),\n at: moment(\"at\").notNull(),\n },\n (table) => [index(\"messenger_member_hits_member_idx\").on(table.messenger, table.botId, table.externalChatId, table.externalUserId, table.at)],\n);\n\n/**\n * Принятые обновления мессенджера: повтор с тем же ключом второй раз не обрабатывается. Необработанное обновление\n * лежит с содержимым за копией сервера до `lease_until`: упала — его забирает другая копия.\n */\nexport const messengerUpdates = agentChatSchema.table(\n \"messenger_updates\",\n {\n key: text(\"key\").primaryKey(),\n status: text(\"status\", { enum: [\"received\", \"done\", \"failed\"] }).notNull(),\n receivedAt: moment(\"received_at\").notNull(),\n finishedAt: moment(\"finished_at\"),\n // Обновление в работе: чьё, содержимое, за кем до какого срока, сколько раз забрано. У метки и законченного — пусто.\n messenger: text(\"messenger\", { enum: [\"telegram\", \"vk\", \"max\"] }),\n botId: text(\"bot_id\"),\n payload: jsonb(\"payload\"),\n holder: text(\"holder\"),\n leaseUntil: moment(\"lease_until\"),\n attempts: integer(\"attempts\").notNull().default(0),\n },\n (table) => [index(\"messenger_updates_received_idx\").on(table.receivedAt), index(\"messenger_updates_work_idx\").on(table.messenger, table.botId, table.leaseUntil)],\n);\n\n/** Кнопки под сообщениями: короткий номер → действие (в кнопку мессенджера длинное действие не влезает). */\nexport const messengerButtons = agentChatSchema.table(\n \"messenger_buttons\",\n {\n id: text(\"id\").primaryKey(),\n messenger: messengerColumn(),\n botId: text(\"bot_id\").notNull(),\n externalChatId: text(\"external_chat_id\").notNull(),\n action: jsonb(\"action\").notNull(),\n createdAt: moment(\"created_at\").notNull(),\n usedAt: moment(\"used_at\"),\n },\n (table) => [index(\"messenger_buttons_created_idx\").on(table.createdAt)],\n);\n\n/** Аренда опроса: один опрашивающий на бота из всех копий сервера. */\nexport const messengerLeases = agentChatSchema.table(\"messenger_leases\", {\n name: text(\"name\").primaryKey(),\n holder: text(\"holder\").notNull(),\n leaseUntil: moment(\"lease_until\").notNull(),\n});\n\n/** Очередь отправки: итоги, ещё не ушедшие в мессенджер, за копией сервера до `lease_until`. Порядок — `seq`. */\nexport const messengerDeliveries = agentChatSchema.table(\n \"messenger_deliveries\",\n {\n id: text(\"id\").primaryKey(),\n seq: bigserial(\"seq\", { mode: \"number\" }).notNull(),\n messenger: messengerColumn(),\n botId: text(\"bot_id\").notNull(),\n externalChatId: text(\"external_chat_id\").notNull(),\n payload: jsonb(\"payload\").notNull(),\n holder: text(\"holder\").notNull(),\n leaseUntil: moment(\"lease_until\").notNull(),\n createdAt: moment(\"created_at\").notNull(),\n },\n (table) => [index(\"messenger_deliveries_bot_idx\").on(table.messenger, table.botId, table.leaseUntil), index(\"messenger_deliveries_holder_idx\").on(table.holder)],\n);\n"],"mappings":"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","names":["migrationsFolder","createPostgresMessengerStore"]}
1
+ {"version":3,"sources":["../src/postgres.ts","../src/store/postgres-store.ts","../src/store/postgres-schema.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport pg from \"pg\";\nimport { createPostgresMessengerStore as openPostgresMessengerStore, migrateMessengersToLatest } from \"./store/postgres-store\";\nimport type { ClosableMessengerStore } from \"./types\";\n\nexport { MESSENGERS_MIGRATIONS_TABLE, MESSENGERS_MIGRATION_LOCK_KEY } from \"./store/migration-settings\";\nexport * from \"./store/postgres-schema\";\nexport type { ClosableMessengerStore, MessengerStore } from \"./types\";\n\nconst DEFAULT_POOL_SIZE = 5;\nconst migrationsFolder = fileURLToPath(new URL(\"../drizzle/postgres\", import.meta.url));\n\nexport interface PostgresMessengerStoreOptions {\n /** Например `postgres://user:password@host:5432/database`; та же база, что у `createPostgresChatStorage`. */\n connectionString: string;\n /** Сколько соединений держать открытыми. По умолчанию 5. */\n poolSize?: number;\n logError?(message: string): void;\n}\n\n/**\n * Хранилище мессенджеров в PostgreSQL. Таблицы — в схеме `agent_chat`; схема приводится к последней миграции под блокировкой базы.\n * Пул соединений хранилище открывает само; `close()` его закрывает (зовите при остановке сервера после `messengers.stop()`).\n * Не удалось подготовить хранилище — пул закрывается до ошибки.\n */\nexport async function createPostgresMessengerStore(options: PostgresMessengerStoreOptions): Promise<ClosableMessengerStore> {\n const logError = options.logError ?? ((message: string) => console.error(message));\n const pool = new pg.Pool({ connectionString: options.connectionString, max: options.poolSize ?? DEFAULT_POOL_SIZE });\n // Без обработчика оборвавшееся соединение простаивающего пула роняет процесс.\n pool.on(\"error\", (error) => logError(`мессенджеры: соединение с PostgreSQL оборвалось: ${error.message}`));\n try {\n await migrateMessengersToLatest(pool, migrationsFolder);\n } catch (error) {\n await pool.end().catch(() => {});\n const reason = error instanceof Error ? error.message : String(error);\n throw new Error(`Не удалось подготовить хранилище мессенджеров в PostgreSQL: ${reason.split(options.connectionString).join(\"<строка подключения>\")}`);\n }\n return openPostgresMessengerStore(pool);\n}\n","import { and, asc, count, eq, gt, gte, inArray, isNull, lt, lte, or, sql } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/node-postgres\";\nimport { migrate } from \"drizzle-orm/node-postgres/migrator\";\nimport { migratePostgresSchema } from \"@ykurilov/agent-chat-core\";\nimport type { Pool } from \"pg\";\nimport type { ButtonAction, ClosableMessengerStore, ConversationKey, MessengerConversation, MessengerLink, PendingDelivery } from \"../types\";\nimport { MESSENGERS_MIGRATION_LOCK_KEY, MESSENGERS_MIGRATIONS_TABLE } from \"./migration-settings\";\nimport { messengerButtons, messengerConversations, messengerDeliveries, messengerLeases, messengerLinkCodes, messengerLinks, messengerMemberHits, messengerUpdates } from \"./postgres-schema\";\n\nconst date = (iso: string) => new Date(iso);\n\n/**\n * Postgres не хранит символ NUL ни в тексте, ни в jsonb: запись с ним падает. Строки от мессенджера и модели (имя,\n * содержимое обновления, текст итога, действие кнопки) пишутся без него.\n */\nfunction withoutNul<T>(value: T): T {\n if (typeof value === \"string\") return value.replaceAll(\"\\u0000\", \"\") as T;\n if (Array.isArray(value)) return value.map(withoutNul) as T;\n if (value !== null && typeof value === \"object\" && Object.getPrototypeOf(value) === Object.prototype) {\n return Object.fromEntries(Object.entries(value).map(([field, item]) => [withoutNul(field), withoutNul(item)])) as T;\n }\n return value;\n}\n\n/**\n * Приводит таблицы мессенджеров к последней миграции. Схема `agent_chat` создаётся под общей блокировкой всех пакетов, миграции — под своей:\n * копии пакета ждут друг друга, а разные пакеты стартуют разом на пустой базе без гонки за схему.\n */\nexport async function migrateMessengersToLatest(pool: Pool, migrationsFolder: string): Promise<void> {\n await migratePostgresSchema({\n connect: () => pool.connect(),\n lockKey: MESSENGERS_MIGRATION_LOCK_KEY,\n migrate: (client) => migrate(drizzle(client), { migrationsFolder, migrationsSchema: \"agent_chat\", migrationsTable: MESSENGERS_MIGRATIONS_TABLE }),\n });\n}\n\n/** Хранилище мессенджеров в PostgreSQL (схема `agent_chat`). Одноразовое берётся одним условным запросом. Закрытие закрывает пул, повторное — ничего не делает. */\nexport function createPostgresMessengerStore(pool: Pool): ClosableMessengerStore {\n const db = drizzle(pool);\n let closing: Promise<void> | undefined;\n\n const conversationIs = (key: ConversationKey) =>\n and(eq(messengerConversations.messenger, key.messenger), eq(messengerConversations.botId, key.botId), eq(messengerConversations.externalChatId, key.externalChatId));\n\n return {\n async saveLinkCode(code) {\n await db.insert(messengerLinkCodes).values({ codeHash: code.codeHash, userId: code.userId, messenger: code.messenger, expiresAt: date(code.expiresAt) });\n },\n async consumeLinkCode(codeHash, messenger, now) {\n const [row] = await db\n .update(messengerLinkCodes)\n .set({ usedAt: date(now) })\n .where(\n and(\n eq(messengerLinkCodes.codeHash, codeHash),\n eq(messengerLinkCodes.messenger, messenger),\n isNull(messengerLinkCodes.usedAt),\n gt(messengerLinkCodes.expiresAt, date(now)),\n ),\n )\n .returning({ userId: messengerLinkCodes.userId });\n return row ?? null;\n },\n async saveLink(link) {\n const values = withoutNul({ ...link, username: link.username ?? null, linkedAt: date(link.linkedAt) });\n await db\n .insert(messengerLinks)\n .values(values)\n .onConflictDoUpdate({ target: [messengerLinks.messenger, messengerLinks.botId, messengerLinks.externalUserId], set: values });\n },\n async findLink(messenger, botId, externalUserId) {\n const [row] = await db\n .select()\n .from(messengerLinks)\n .where(and(eq(messengerLinks.messenger, messenger), eq(messengerLinks.botId, botId), eq(messengerLinks.externalUserId, externalUserId)));\n return row ? toLink(row) : null;\n },\n async listLinks(userId) {\n return (await db.select().from(messengerLinks).where(eq(messengerLinks.userId, userId)).orderBy(asc(messengerLinks.linkedAt))).map(toLink);\n },\n async removeLinks(userId, messenger) {\n await db.delete(messengerLinks).where(and(eq(messengerLinks.userId, userId), eq(messengerLinks.messenger, messenger)));\n },\n async getConversation(key) {\n const [row] = await db.select().from(messengerConversations).where(conversationIs(key));\n return row ? toConversation(row) : null;\n },\n async saveConversation(conversation) {\n const values = {\n ...conversation,\n connectedBy: conversation.connectedBy ?? null,\n currentChatId: conversation.currentChatId ?? null,\n createdAt: date(conversation.createdAt),\n updatedAt: date(conversation.updatedAt),\n };\n await db\n .insert(messengerConversations)\n .values(values)\n .onConflictDoUpdate({ target: [messengerConversations.messenger, messengerConversations.botId, messengerConversations.externalChatId], set: values });\n },\n async setCurrentChat(key, chatId, at) {\n await db.update(messengerConversations).set({ currentChatId: chatId, updatedAt: date(at) }).where(conversationIs(key));\n },\n async setBlocked(key, blocked, at) {\n await db.update(messengerConversations).set({ blocked, updatedAt: date(at) }).where(conversationIs(key));\n },\n async setActive(key, active, at) {\n await db.update(messengerConversations).set({ active, updatedAt: date(at) }).where(conversationIs(key));\n },\n async listConversationsOfChat(chatId) {\n return (await db.select().from(messengerConversations).where(eq(messengerConversations.currentChatId, chatId))).map(toConversation);\n },\n async listConversationsOfUser(userId) {\n return (await db.select().from(messengerConversations).where(eq(messengerConversations.userId, userId))).map(toConversation);\n },\n async addMemberHit(hit) {\n await db\n .insert(messengerMemberHits)\n .values({ ...hit, at: date(hit.at) })\n .onConflictDoNothing();\n },\n async countMemberHits(key, externalUserId, since) {\n const [row] = await db\n .select({ value: count() })\n .from(messengerMemberHits)\n .where(\n and(\n eq(messengerMemberHits.messenger, key.messenger),\n eq(messengerMemberHits.botId, key.botId),\n eq(messengerMemberHits.externalChatId, key.externalChatId),\n eq(messengerMemberHits.externalUserId, externalUserId),\n gte(messengerMemberHits.at, date(since)),\n ),\n );\n return Number(row?.value ?? 0);\n },\n async claimUpdate(key, now, work) {\n const content = work && { messenger: work.messenger, botId: work.botId, payload: withoutNul(work.update), holder: work.holder, leaseUntil: date(work.leaseUntil) };\n const rows = await db\n .insert(messengerUpdates)\n .values({ key, status: \"received\", receivedAt: date(now), ...content })\n .onConflictDoNothing()\n .returning({ key: messengerUpdates.key });\n return rows.length === 1;\n },\n async finishUpdate(key, status, now) {\n await db.update(messengerUpdates).set({ status, finishedAt: date(now), payload: null, holder: null, leaseUntil: null }).where(eq(messengerUpdates.key, key));\n },\n async renewUpdates(keys, holder, leaseUntil) {\n if (keys.length === 0) return;\n await db\n .update(messengerUpdates)\n .set({ leaseUntil: date(leaseUntil) })\n .where(and(inArray(messengerUpdates.key, [...keys]), eq(messengerUpdates.holder, holder), eq(messengerUpdates.status, \"received\")));\n },\n async releaseUpdate(key, holder, retryAt) {\n await db\n .update(messengerUpdates)\n .set({ holder: \"\", leaseUntil: date(retryAt) })\n .where(and(eq(messengerUpdates.key, key), eq(messengerUpdates.holder, holder), eq(messengerUpdates.status, \"received\")));\n },\n async takeAbandonedUpdates(bot, holder, now, leaseUntil, limit) {\n const abandoned = and(\n eq(messengerUpdates.messenger, bot.messenger),\n eq(messengerUpdates.botId, bot.botId),\n eq(messengerUpdates.status, \"received\"),\n lte(messengerUpdates.leaseUntil, date(now)),\n );\n // Как у итогов: занятые другой копией строки пропускаются, выбор — отдельным шагом (WITH).\n const oldest = db\n .$with(\"oldest_updates\")\n .as(db.select({ key: messengerUpdates.key }).from(messengerUpdates).where(abandoned).orderBy(asc(messengerUpdates.receivedAt), asc(messengerUpdates.key)).limit(limit).for(\"update\", { skipLocked: true }));\n const rows = await db\n .with(oldest)\n .update(messengerUpdates)\n .set({ holder, leaseUntil: date(leaseUntil), attempts: sql`${messengerUpdates.attempts} + 1` })\n .where(inArray(messengerUpdates.key, db.select({ key: oldest.key }).from(oldest)))\n .returning({ key: messengerUpdates.key, update: messengerUpdates.payload, receivedAt: messengerUpdates.receivedAt, attempts: messengerUpdates.attempts });\n return rows\n .sort((left, right) => left.receivedAt.getTime() - right.receivedAt.getTime() || (left.key < right.key ? -1 : 1))\n .map(({ key, update, receivedAt, attempts }) => ({ key, update, receivedAt: receivedAt.toISOString(), attempts }));\n },\n async saveButton(button) {\n await db\n .insert(messengerButtons)\n .values({ id: button.id, messenger: button.messenger, botId: button.botId, externalChatId: button.externalChatId, action: withoutNul(button.action), createdAt: date(button.createdAt) });\n },\n async takeButton(key, id, now) {\n const [row] = await db\n .update(messengerButtons)\n .set({ usedAt: date(now) })\n .where(\n and(\n eq(messengerButtons.id, id),\n eq(messengerButtons.messenger, key.messenger),\n eq(messengerButtons.botId, key.botId),\n eq(messengerButtons.externalChatId, key.externalChatId),\n isNull(messengerButtons.usedAt),\n ),\n )\n .returning({ action: messengerButtons.action });\n return row ? (row.action as ButtonAction) : null;\n },\n async savePendingDelivery(delivery, holder, leaseUntil) {\n const { id, messenger, botId, externalChatId, createdAt, ...payload } = delivery;\n const values = { id, messenger, botId, externalChatId, payload: withoutNul(payload), holder, leaseUntil: date(leaseUntil), createdAt: date(createdAt) };\n await db.insert(messengerDeliveries).values(values).onConflictDoUpdate({ target: messengerDeliveries.id, set: values });\n },\n async deletePendingDelivery(id) {\n await db.delete(messengerDeliveries).where(eq(messengerDeliveries.id, id));\n },\n async renewPendingDeliveries(holder, leaseUntil) {\n await db.update(messengerDeliveries).set({ leaseUntil: date(leaseUntil) }).where(eq(messengerDeliveries.holder, holder));\n },\n async takeAbandonedDeliveries(bot, holder, now, leaseUntil, limit) {\n const abandoned = and(eq(messengerDeliveries.messenger, bot.messenger), eq(messengerDeliveries.botId, bot.botId), lte(messengerDeliveries.leaseUntil, date(now)));\n // Строки, которые уже забирает другая копия, пропускаются: каждая достаётся одной. Выбор — отдельным шагом (WITH),\n // иначе PostgreSQL может пересчитать LIMIT по ходу обновления.\n const oldest = db\n .$with(\"oldest\")\n .as(db.select({ id: messengerDeliveries.id }).from(messengerDeliveries).where(abandoned).orderBy(asc(messengerDeliveries.seq)).limit(limit).for(\"update\", { skipLocked: true }));\n const rows = await db\n .with(oldest)\n .update(messengerDeliveries)\n .set({ holder, leaseUntil: date(leaseUntil) })\n .where(inArray(messengerDeliveries.id, db.select({ id: oldest.id }).from(oldest)))\n .returning();\n return rows.sort((left, right) => left.seq - right.seq).map(toDelivery);\n },\n async acquireLease(name, holder, leaseUntil, now) {\n const rows = await db\n .insert(messengerLeases)\n .values({ name, holder, leaseUntil: date(leaseUntil) })\n .onConflictDoUpdate({\n target: messengerLeases.name,\n set: { holder, leaseUntil: date(leaseUntil) },\n setWhere: or(eq(messengerLeases.holder, holder), lte(messengerLeases.leaseUntil, date(now))),\n })\n .returning({ name: messengerLeases.name });\n return rows.length === 1;\n },\n async releaseLease(name, holder) {\n await db.delete(messengerLeases).where(and(eq(messengerLeases.name, name), eq(messengerLeases.holder, holder)));\n },\n async purgeBefore(before) {\n const cutoff = date(before);\n await db.transaction(async (tx) => {\n await tx.delete(messengerLinkCodes).where(lt(messengerLinkCodes.expiresAt, cutoff));\n // Обновление с живой арендой не убирается: его ещё подберут или бросят. Аренда, истёкшая раньше срока хранения, —\n // забытое обновление (например, бот убран из настроек): его уже никто не подберёт.\n await tx.delete(messengerUpdates).where(and(lt(messengerUpdates.receivedAt, cutoff), or(isNull(messengerUpdates.holder), lt(messengerUpdates.leaseUntil, cutoff))));\n await tx.delete(messengerButtons).where(lt(messengerButtons.createdAt, cutoff));\n await tx.delete(messengerMemberHits).where(lt(messengerMemberHits.at, cutoff));\n });\n },\n close() {\n closing ??= pool.end();\n return closing;\n },\n };\n}\n\nfunction toLink(row: typeof messengerLinks.$inferSelect): MessengerLink {\n const { username, linkedAt, ...rest } = row;\n return { ...rest, ...(username !== null && { username }), linkedAt: linkedAt.toISOString() };\n}\n\nfunction toConversation(row: typeof messengerConversations.$inferSelect): MessengerConversation {\n const { connectedBy, currentChatId, createdAt, updatedAt, ...rest } = row;\n return {\n ...rest,\n ...(connectedBy !== null && { connectedBy }),\n ...(currentChatId !== null && { currentChatId }),\n createdAt: createdAt.toISOString(),\n updatedAt: updatedAt.toISOString(),\n };\n}\n\nfunction toDelivery(row: typeof messengerDeliveries.$inferSelect): PendingDelivery {\n return {\n id: row.id,\n messenger: row.messenger,\n botId: row.botId,\n externalChatId: row.externalChatId,\n ...(row.payload as Omit<PendingDelivery, \"id\" | \"createdAt\" | keyof ConversationKey>),\n createdAt: row.createdAt.toISOString(),\n };\n}\n","import { bigserial, boolean, index, integer, jsonb, pgSchema, primaryKey, text, timestamp } from \"drizzle-orm/pg-core\";\n\n/** Та же схема базы, что у хранилища чатов: у приложения одна база и одна схема `agent_chat`. */\nconst agentChatSchema = pgSchema(\"agent_chat\");\n\nconst moment = (name: string) => timestamp(name, { mode: \"date\", precision: 3, withTimezone: true });\nconst messengerColumn = () => text(\"messenger\", { enum: [\"telegram\", \"vk\", \"max\"] }).notNull();\n\n/** Привязанный аккаунт: человек в мессенджере ↔ пользователь приложения. */\nexport const messengerLinks = agentChatSchema.table(\n \"messenger_links\",\n {\n messenger: messengerColumn(),\n botId: text(\"bot_id\").notNull(),\n externalUserId: text(\"external_user_id\").notNull(),\n userId: text(\"user_id\").notNull(),\n name: text(\"name\").notNull(),\n username: text(\"username\"),\n linkedAt: moment(\"linked_at\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.messenger, table.botId, table.externalUserId] }), index(\"messenger_links_user_idx\").on(table.userId, table.messenger)],\n);\n\n/** Одноразовые коды привязки: хранится только хеш кода. */\nexport const messengerLinkCodes = agentChatSchema.table(\n \"messenger_link_codes\",\n {\n codeHash: text(\"code_hash\").primaryKey(),\n userId: text(\"user_id\").notNull(),\n messenger: messengerColumn(),\n expiresAt: moment(\"expires_at\").notNull(),\n usedAt: moment(\"used_at\"),\n },\n (table) => [index(\"messenger_link_codes_expires_idx\").on(table.expiresAt)],\n);\n\n/** Внешний чат (личный или группа): кому он засчитывается, какой чат агента сейчас ведётся, можно ли писать. */\nexport const messengerConversations = agentChatSchema.table(\n \"messenger_conversations\",\n {\n messenger: messengerColumn(),\n botId: text(\"bot_id\").notNull(),\n externalChatId: text(\"external_chat_id\").notNull(),\n type: text(\"type\", { enum: [\"private\", \"group\"] }).notNull(),\n /** Личный чат — привязанный пользователь; группа — плательщик. */\n userId: text(\"user_id\").notNull(),\n /** Группа: кто подключил бота. */\n connectedBy: text(\"connected_by\"),\n currentChatId: text(\"current_chat_id\"),\n /** Человек заблокировал бота: писать ему нельзя. */\n blocked: boolean(\"blocked\").notNull().default(false),\n /** `false` — бота убрали из группы. */\n active: boolean(\"active\").notNull().default(true),\n createdAt: moment(\"created_at\").notNull(),\n updatedAt: moment(\"updated_at\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.messenger, table.botId, table.externalChatId] }), index(\"messenger_conversations_user_idx\").on(table.userId), index(\"messenger_conversations_current_chat_idx\").on(table.currentChatId)],\n);\n\n/** Обращения участника группы к боту: по ним считается предел за час. */\nexport const messengerMemberHits = agentChatSchema.table(\n \"messenger_member_hits\",\n {\n id: text(\"id\").primaryKey(),\n messenger: messengerColumn(),\n botId: text(\"bot_id\").notNull(),\n externalChatId: text(\"external_chat_id\").notNull(),\n externalUserId: text(\"external_user_id\").notNull(),\n at: moment(\"at\").notNull(),\n },\n (table) => [index(\"messenger_member_hits_member_idx\").on(table.messenger, table.botId, table.externalChatId, table.externalUserId, table.at)],\n);\n\n/**\n * Принятые обновления мессенджера: повтор с тем же ключом второй раз не обрабатывается. Необработанное обновление\n * лежит с содержимым за копией сервера до `lease_until`: упала — его забирает другая копия.\n */\nexport const messengerUpdates = agentChatSchema.table(\n \"messenger_updates\",\n {\n key: text(\"key\").primaryKey(),\n status: text(\"status\", { enum: [\"received\", \"done\", \"failed\"] }).notNull(),\n receivedAt: moment(\"received_at\").notNull(),\n finishedAt: moment(\"finished_at\"),\n // Обновление в работе: чьё, содержимое, за кем до какого срока, сколько раз забрано. У метки и законченного — пусто.\n messenger: text(\"messenger\", { enum: [\"telegram\", \"vk\", \"max\"] }),\n botId: text(\"bot_id\"),\n payload: jsonb(\"payload\"),\n holder: text(\"holder\"),\n leaseUntil: moment(\"lease_until\"),\n attempts: integer(\"attempts\").notNull().default(0),\n },\n (table) => [index(\"messenger_updates_received_idx\").on(table.receivedAt), index(\"messenger_updates_work_idx\").on(table.messenger, table.botId, table.leaseUntil)],\n);\n\n/** Кнопки под сообщениями: короткий номер → действие (в кнопку мессенджера длинное действие не влезает). */\nexport const messengerButtons = agentChatSchema.table(\n \"messenger_buttons\",\n {\n id: text(\"id\").primaryKey(),\n messenger: messengerColumn(),\n botId: text(\"bot_id\").notNull(),\n externalChatId: text(\"external_chat_id\").notNull(),\n action: jsonb(\"action\").notNull(),\n createdAt: moment(\"created_at\").notNull(),\n usedAt: moment(\"used_at\"),\n },\n (table) => [index(\"messenger_buttons_created_idx\").on(table.createdAt)],\n);\n\n/** Аренда опроса: один опрашивающий на бота из всех копий сервера. */\nexport const messengerLeases = agentChatSchema.table(\"messenger_leases\", {\n name: text(\"name\").primaryKey(),\n holder: text(\"holder\").notNull(),\n leaseUntil: moment(\"lease_until\").notNull(),\n});\n\n/** Очередь отправки: итоги, ещё не ушедшие в мессенджер, за копией сервера до `lease_until`. Порядок — `seq`. */\nexport const messengerDeliveries = agentChatSchema.table(\n \"messenger_deliveries\",\n {\n id: text(\"id\").primaryKey(),\n seq: bigserial(\"seq\", { mode: \"number\" }).notNull(),\n messenger: messengerColumn(),\n botId: text(\"bot_id\").notNull(),\n externalChatId: text(\"external_chat_id\").notNull(),\n payload: jsonb(\"payload\").notNull(),\n holder: text(\"holder\").notNull(),\n leaseUntil: moment(\"lease_until\").notNull(),\n createdAt: moment(\"created_at\").notNull(),\n },\n (table) => [index(\"messenger_deliveries_bot_idx\").on(table.messenger, table.botId, table.leaseUntil), index(\"messenger_deliveries_holder_idx\").on(table.holder)],\n);\n"],"mappings":";;;;;;AAAA,SAAS,qBAAqB;AAC9B,OAAO,QAAQ;;;ACDf,SAAS,KAAK,KAAK,OAAO,IAAI,IAAI,KAAK,SAAS,QAAQ,IAAI,KAAK,IAAI,WAAW;AAChF,SAAS,eAAe;AACxB,SAAS,eAAe;AACxB,SAAS,6BAA6B;;;ACHtC,SAAS,WAAW,SAAS,OAAO,SAAS,OAAO,UAAU,YAAY,MAAM,iBAAiB;AAGjG,IAAM,kBAAkB,SAAS,YAAY;AAE7C,IAAM,SAAS,CAAC,SAAiB,UAAU,MAAM,EAAE,MAAM,QAAQ,WAAW,GAAG,cAAc,KAAK,CAAC;AACnG,IAAM,kBAAkB,MAAM,KAAK,aAAa,EAAE,MAAM,CAAC,YAAY,MAAM,KAAK,EAAE,CAAC,EAAE,QAAQ;AAGtF,IAAM,iBAAiB,gBAAgB;AAAA,EAC5C;AAAA,EACA;AAAA,IACE,WAAW,gBAAgB;AAAA,IAC3B,OAAO,KAAK,QAAQ,EAAE,QAAQ;AAAA,IAC9B,gBAAgB,KAAK,kBAAkB,EAAE,QAAQ;AAAA,IACjD,QAAQ,KAAK,SAAS,EAAE,QAAQ;AAAA,IAChC,MAAM,KAAK,MAAM,EAAE,QAAQ;AAAA,IAC3B,UAAU,KAAK,UAAU;AAAA,IACzB,UAAU,OAAO,WAAW,EAAE,QAAQ;AAAA,EACxC;AAAA,EACA,CAAC,UAAU,CAAC,WAAW,EAAE,SAAS,CAAC,MAAM,WAAW,MAAM,OAAO,MAAM,cAAc,EAAE,CAAC,GAAG,MAAM,0BAA0B,EAAE,GAAG,MAAM,QAAQ,MAAM,SAAS,CAAC;AAChK;AAGO,IAAM,qBAAqB,gBAAgB;AAAA,EAChD;AAAA,EACA;AAAA,IACE,UAAU,KAAK,WAAW,EAAE,WAAW;AAAA,IACvC,QAAQ,KAAK,SAAS,EAAE,QAAQ;AAAA,IAChC,WAAW,gBAAgB;AAAA,IAC3B,WAAW,OAAO,YAAY,EAAE,QAAQ;AAAA,IACxC,QAAQ,OAAO,SAAS;AAAA,EAC1B;AAAA,EACA,CAAC,UAAU,CAAC,MAAM,kCAAkC,EAAE,GAAG,MAAM,SAAS,CAAC;AAC3E;AAGO,IAAM,yBAAyB,gBAAgB;AAAA,EACpD;AAAA,EACA;AAAA,IACE,WAAW,gBAAgB;AAAA,IAC3B,OAAO,KAAK,QAAQ,EAAE,QAAQ;AAAA,IAC9B,gBAAgB,KAAK,kBAAkB,EAAE,QAAQ;AAAA,IACjD,MAAM,KAAK,QAAQ,EAAE,MAAM,CAAC,WAAW,OAAO,EAAE,CAAC,EAAE,QAAQ;AAAA;AAAA,IAE3D,QAAQ,KAAK,SAAS,EAAE,QAAQ;AAAA;AAAA,IAEhC,aAAa,KAAK,cAAc;AAAA,IAChC,eAAe,KAAK,iBAAiB;AAAA;AAAA,IAErC,SAAS,QAAQ,SAAS,EAAE,QAAQ,EAAE,QAAQ,KAAK;AAAA;AAAA,IAEnD,QAAQ,QAAQ,QAAQ,EAAE,QAAQ,EAAE,QAAQ,IAAI;AAAA,IAChD,WAAW,OAAO,YAAY,EAAE,QAAQ;AAAA,IACxC,WAAW,OAAO,YAAY,EAAE,QAAQ;AAAA,EAC1C;AAAA,EACA,CAAC,UAAU,CAAC,WAAW,EAAE,SAAS,CAAC,MAAM,WAAW,MAAM,OAAO,MAAM,cAAc,EAAE,CAAC,GAAG,MAAM,kCAAkC,EAAE,GAAG,MAAM,MAAM,GAAG,MAAM,0CAA0C,EAAE,GAAG,MAAM,aAAa,CAAC;AAClO;AAGO,IAAM,sBAAsB,gBAAgB;AAAA,EACjD;AAAA,EACA;AAAA,IACE,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,IAC1B,WAAW,gBAAgB;AAAA,IAC3B,OAAO,KAAK,QAAQ,EAAE,QAAQ;AAAA,IAC9B,gBAAgB,KAAK,kBAAkB,EAAE,QAAQ;AAAA,IACjD,gBAAgB,KAAK,kBAAkB,EAAE,QAAQ;AAAA,IACjD,IAAI,OAAO,IAAI,EAAE,QAAQ;AAAA,EAC3B;AAAA,EACA,CAAC,UAAU,CAAC,MAAM,kCAAkC,EAAE,GAAG,MAAM,WAAW,MAAM,OAAO,MAAM,gBAAgB,MAAM,gBAAgB,MAAM,EAAE,CAAC;AAC9I;AAMO,IAAM,mBAAmB,gBAAgB;AAAA,EAC9C;AAAA,EACA;AAAA,IACE,KAAK,KAAK,KAAK,EAAE,WAAW;AAAA,IAC5B,QAAQ,KAAK,UAAU,EAAE,MAAM,CAAC,YAAY,QAAQ,QAAQ,EAAE,CAAC,EAAE,QAAQ;AAAA,IACzE,YAAY,OAAO,aAAa,EAAE,QAAQ;AAAA,IAC1C,YAAY,OAAO,aAAa;AAAA;AAAA,IAEhC,WAAW,KAAK,aAAa,EAAE,MAAM,CAAC,YAAY,MAAM,KAAK,EAAE,CAAC;AAAA,IAChE,OAAO,KAAK,QAAQ;AAAA,IACpB,SAAS,MAAM,SAAS;AAAA,IACxB,QAAQ,KAAK,QAAQ;AAAA,IACrB,YAAY,OAAO,aAAa;AAAA,IAChC,UAAU,QAAQ,UAAU,EAAE,QAAQ,EAAE,QAAQ,CAAC;AAAA,EACnD;AAAA,EACA,CAAC,UAAU,CAAC,MAAM,gCAAgC,EAAE,GAAG,MAAM,UAAU,GAAG,MAAM,4BAA4B,EAAE,GAAG,MAAM,WAAW,MAAM,OAAO,MAAM,UAAU,CAAC;AAClK;AAGO,IAAM,mBAAmB,gBAAgB;AAAA,EAC9C;AAAA,EACA;AAAA,IACE,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,IAC1B,WAAW,gBAAgB;AAAA,IAC3B,OAAO,KAAK,QAAQ,EAAE,QAAQ;AAAA,IAC9B,gBAAgB,KAAK,kBAAkB,EAAE,QAAQ;AAAA,IACjD,QAAQ,MAAM,QAAQ,EAAE,QAAQ;AAAA,IAChC,WAAW,OAAO,YAAY,EAAE,QAAQ;AAAA,IACxC,QAAQ,OAAO,SAAS;AAAA,EAC1B;AAAA,EACA,CAAC,UAAU,CAAC,MAAM,+BAA+B,EAAE,GAAG,MAAM,SAAS,CAAC;AACxE;AAGO,IAAM,kBAAkB,gBAAgB,MAAM,oBAAoB;AAAA,EACvE,MAAM,KAAK,MAAM,EAAE,WAAW;AAAA,EAC9B,QAAQ,KAAK,QAAQ,EAAE,QAAQ;AAAA,EAC/B,YAAY,OAAO,aAAa,EAAE,QAAQ;AAC5C,CAAC;AAGM,IAAM,sBAAsB,gBAAgB;AAAA,EACjD;AAAA,EACA;AAAA,IACE,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,IAC1B,KAAK,UAAU,OAAO,EAAE,MAAM,SAAS,CAAC,EAAE,QAAQ;AAAA,IAClD,WAAW,gBAAgB;AAAA,IAC3B,OAAO,KAAK,QAAQ,EAAE,QAAQ;AAAA,IAC9B,gBAAgB,KAAK,kBAAkB,EAAE,QAAQ;AAAA,IACjD,SAAS,MAAM,SAAS,EAAE,QAAQ;AAAA,IAClC,QAAQ,KAAK,QAAQ,EAAE,QAAQ;AAAA,IAC/B,YAAY,OAAO,aAAa,EAAE,QAAQ;AAAA,IAC1C,WAAW,OAAO,YAAY,EAAE,QAAQ;AAAA,EAC1C;AAAA,EACA,CAAC,UAAU,CAAC,MAAM,8BAA8B,EAAE,GAAG,MAAM,WAAW,MAAM,OAAO,MAAM,UAAU,GAAG,MAAM,iCAAiC,EAAE,GAAG,MAAM,MAAM,CAAC;AACjK;;;AD3HA,IAAM,OAAO,CAAC,QAAgB,IAAI,KAAK,GAAG;AAM1C,SAAS,WAAc,OAAa;AAClC,MAAI,OAAO,UAAU,SAAU,QAAO,MAAM,WAAW,MAAU,EAAE;AACnE,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAI,UAAU;AACrD,MAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,OAAO,eAAe,KAAK,MAAM,OAAO,WAAW;AACpG,WAAO,OAAO,YAAY,OAAO,QAAQ,KAAK,EAAE,IAAI,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,WAAW,KAAK,GAAG,WAAW,IAAI,CAAC,CAAC,CAAC;AAAA,EAC/G;AACA,SAAO;AACT;AAMA,eAAsB,0BAA0B,MAAYA,mBAAyC;AACnG,QAAM,sBAAsB;AAAA,IAC1B,SAAS,MAAM,KAAK,QAAQ;AAAA,IAC5B,SAAS;AAAA,IACT,SAAS,CAAC,WAAW,QAAQ,QAAQ,MAAM,GAAG,EAAE,kBAAAA,mBAAkB,kBAAkB,cAAc,iBAAiB,4BAA4B,CAAC;AAAA,EAClJ,CAAC;AACH;AAGO,SAAS,6BAA6B,MAAoC;AAC/E,QAAM,KAAK,QAAQ,IAAI;AACvB,MAAI;AAEJ,QAAM,iBAAiB,CAAC,QACtB,IAAI,GAAG,uBAAuB,WAAW,IAAI,SAAS,GAAG,GAAG,uBAAuB,OAAO,IAAI,KAAK,GAAG,GAAG,uBAAuB,gBAAgB,IAAI,cAAc,CAAC;AAErK,SAAO;AAAA,IACL,MAAM,aAAa,MAAM;AACvB,YAAM,GAAG,OAAO,kBAAkB,EAAE,OAAO,EAAE,UAAU,KAAK,UAAU,QAAQ,KAAK,QAAQ,WAAW,KAAK,WAAW,WAAW,KAAK,KAAK,SAAS,EAAE,CAAC;AAAA,IACzJ;AAAA,IACA,MAAM,gBAAgB,UAAU,WAAW,KAAK;AAC9C,YAAM,CAAC,GAAG,IAAI,MAAM,GACjB,OAAO,kBAAkB,EACzB,IAAI,EAAE,QAAQ,KAAK,GAAG,EAAE,CAAC,EACzB;AAAA,QACC;AAAA,UACE,GAAG,mBAAmB,UAAU,QAAQ;AAAA,UACxC,GAAG,mBAAmB,WAAW,SAAS;AAAA,UAC1C,OAAO,mBAAmB,MAAM;AAAA,UAChC,GAAG,mBAAmB,WAAW,KAAK,GAAG,CAAC;AAAA,QAC5C;AAAA,MACF,EACC,UAAU,EAAE,QAAQ,mBAAmB,OAAO,CAAC;AAClD,aAAO,OAAO;AAAA,IAChB;AAAA,IACA,MAAM,SAAS,MAAM;AACnB,YAAM,SAAS,WAAW,EAAE,GAAG,MAAM,UAAU,KAAK,YAAY,MAAM,UAAU,KAAK,KAAK,QAAQ,EAAE,CAAC;AACrG,YAAM,GACH,OAAO,cAAc,EACrB,OAAO,MAAM,EACb,mBAAmB,EAAE,QAAQ,CAAC,eAAe,WAAW,eAAe,OAAO,eAAe,cAAc,GAAG,KAAK,OAAO,CAAC;AAAA,IAChI;AAAA,IACA,MAAM,SAAS,WAAW,OAAO,gBAAgB;AAC/C,YAAM,CAAC,GAAG,IAAI,MAAM,GACjB,OAAO,EACP,KAAK,cAAc,EACnB,MAAM,IAAI,GAAG,eAAe,WAAW,SAAS,GAAG,GAAG,eAAe,OAAO,KAAK,GAAG,GAAG,eAAe,gBAAgB,cAAc,CAAC,CAAC;AACzI,aAAO,MAAM,OAAO,GAAG,IAAI;AAAA,IAC7B;AAAA,IACA,MAAM,UAAU,QAAQ;AACtB,cAAQ,MAAM,GAAG,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,GAAG,eAAe,QAAQ,MAAM,CAAC,EAAE,QAAQ,IAAI,eAAe,QAAQ,CAAC,GAAG,IAAI,MAAM;AAAA,IAC3I;AAAA,IACA,MAAM,YAAY,QAAQ,WAAW;AACnC,YAAM,GAAG,OAAO,cAAc,EAAE,MAAM,IAAI,GAAG,eAAe,QAAQ,MAAM,GAAG,GAAG,eAAe,WAAW,SAAS,CAAC,CAAC;AAAA,IACvH;AAAA,IACA,MAAM,gBAAgB,KAAK;AACzB,YAAM,CAAC,GAAG,IAAI,MAAM,GAAG,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,eAAe,GAAG,CAAC;AACtF,aAAO,MAAM,eAAe,GAAG,IAAI;AAAA,IACrC;AAAA,IACA,MAAM,iBAAiB,cAAc;AACnC,YAAM,SAAS;AAAA,QACb,GAAG;AAAA,QACH,aAAa,aAAa,eAAe;AAAA,QACzC,eAAe,aAAa,iBAAiB;AAAA,QAC7C,WAAW,KAAK,aAAa,SAAS;AAAA,QACtC,WAAW,KAAK,aAAa,SAAS;AAAA,MACxC;AACA,YAAM,GACH,OAAO,sBAAsB,EAC7B,OAAO,MAAM,EACb,mBAAmB,EAAE,QAAQ,CAAC,uBAAuB,WAAW,uBAAuB,OAAO,uBAAuB,cAAc,GAAG,KAAK,OAAO,CAAC;AAAA,IACxJ;AAAA,IACA,MAAM,eAAe,KAAK,QAAQ,IAAI;AACpC,YAAM,GAAG,OAAO,sBAAsB,EAAE,IAAI,EAAE,eAAe,QAAQ,WAAW,KAAK,EAAE,EAAE,CAAC,EAAE,MAAM,eAAe,GAAG,CAAC;AAAA,IACvH;AAAA,IACA,MAAM,WAAW,KAAK,SAAS,IAAI;AACjC,YAAM,GAAG,OAAO,sBAAsB,EAAE,IAAI,EAAE,SAAS,WAAW,KAAK,EAAE,EAAE,CAAC,EAAE,MAAM,eAAe,GAAG,CAAC;AAAA,IACzG;AAAA,IACA,MAAM,UAAU,KAAK,QAAQ,IAAI;AAC/B,YAAM,GAAG,OAAO,sBAAsB,EAAE,IAAI,EAAE,QAAQ,WAAW,KAAK,EAAE,EAAE,CAAC,EAAE,MAAM,eAAe,GAAG,CAAC;AAAA,IACxG;AAAA,IACA,MAAM,wBAAwB,QAAQ;AACpC,cAAQ,MAAM,GAAG,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,GAAG,uBAAuB,eAAe,MAAM,CAAC,GAAG,IAAI,cAAc;AAAA,IACpI;AAAA,IACA,MAAM,wBAAwB,QAAQ;AACpC,cAAQ,MAAM,GAAG,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,GAAG,uBAAuB,QAAQ,MAAM,CAAC,GAAG,IAAI,cAAc;AAAA,IAC7H;AAAA,IACA,MAAM,aAAa,KAAK;AACtB,YAAM,GACH,OAAO,mBAAmB,EAC1B,OAAO,EAAE,GAAG,KAAK,IAAI,KAAK,IAAI,EAAE,EAAE,CAAC,EACnC,oBAAoB;AAAA,IACzB;AAAA,IACA,MAAM,gBAAgB,KAAK,gBAAgB,OAAO;AAChD,YAAM,CAAC,GAAG,IAAI,MAAM,GACjB,OAAO,EAAE,OAAO,MAAM,EAAE,CAAC,EACzB,KAAK,mBAAmB,EACxB;AAAA,QACC;AAAA,UACE,GAAG,oBAAoB,WAAW,IAAI,SAAS;AAAA,UAC/C,GAAG,oBAAoB,OAAO,IAAI,KAAK;AAAA,UACvC,GAAG,oBAAoB,gBAAgB,IAAI,cAAc;AAAA,UACzD,GAAG,oBAAoB,gBAAgB,cAAc;AAAA,UACrD,IAAI,oBAAoB,IAAI,KAAK,KAAK,CAAC;AAAA,QACzC;AAAA,MACF;AACF,aAAO,OAAO,KAAK,SAAS,CAAC;AAAA,IAC/B;AAAA,IACA,MAAM,YAAY,KAAK,KAAK,MAAM;AAChC,YAAM,UAAU,QAAQ,EAAE,WAAW,KAAK,WAAW,OAAO,KAAK,OAAO,SAAS,WAAW,KAAK,MAAM,GAAG,QAAQ,KAAK,QAAQ,YAAY,KAAK,KAAK,UAAU,EAAE;AACjK,YAAM,OAAO,MAAM,GAChB,OAAO,gBAAgB,EACvB,OAAO,EAAE,KAAK,QAAQ,YAAY,YAAY,KAAK,GAAG,GAAG,GAAG,QAAQ,CAAC,EACrE,oBAAoB,EACpB,UAAU,EAAE,KAAK,iBAAiB,IAAI,CAAC;AAC1C,aAAO,KAAK,WAAW;AAAA,IACzB;AAAA,IACA,MAAM,aAAa,KAAK,QAAQ,KAAK;AACnC,YAAM,GAAG,OAAO,gBAAgB,EAAE,IAAI,EAAE,QAAQ,YAAY,KAAK,GAAG,GAAG,SAAS,MAAM,QAAQ,MAAM,YAAY,KAAK,CAAC,EAAE,MAAM,GAAG,iBAAiB,KAAK,GAAG,CAAC;AAAA,IAC7J;AAAA,IACA,MAAM,aAAa,MAAM,QAAQ,YAAY;AAC3C,UAAI,KAAK,WAAW,EAAG;AACvB,YAAM,GACH,OAAO,gBAAgB,EACvB,IAAI,EAAE,YAAY,KAAK,UAAU,EAAE,CAAC,EACpC,MAAM,IAAI,QAAQ,iBAAiB,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,GAAG,iBAAiB,QAAQ,MAAM,GAAG,GAAG,iBAAiB,QAAQ,UAAU,CAAC,CAAC;AAAA,IACtI;AAAA,IACA,MAAM,cAAc,KAAK,QAAQ,SAAS;AACxC,YAAM,GACH,OAAO,gBAAgB,EACvB,IAAI,EAAE,QAAQ,IAAI,YAAY,KAAK,OAAO,EAAE,CAAC,EAC7C,MAAM,IAAI,GAAG,iBAAiB,KAAK,GAAG,GAAG,GAAG,iBAAiB,QAAQ,MAAM,GAAG,GAAG,iBAAiB,QAAQ,UAAU,CAAC,CAAC;AAAA,IAC3H;AAAA,IACA,MAAM,qBAAqB,KAAK,QAAQ,KAAK,YAAY,OAAO;AAC9D,YAAM,YAAY;AAAA,QAChB,GAAG,iBAAiB,WAAW,IAAI,SAAS;AAAA,QAC5C,GAAG,iBAAiB,OAAO,IAAI,KAAK;AAAA,QACpC,GAAG,iBAAiB,QAAQ,UAAU;AAAA,QACtC,IAAI,iBAAiB,YAAY,KAAK,GAAG,CAAC;AAAA,MAC5C;AAEA,YAAM,SAAS,GACZ,MAAM,gBAAgB,EACtB,GAAG,GAAG,OAAO,EAAE,KAAK,iBAAiB,IAAI,CAAC,EAAE,KAAK,gBAAgB,EAAE,MAAM,SAAS,EAAE,QAAQ,IAAI,iBAAiB,UAAU,GAAG,IAAI,iBAAiB,GAAG,CAAC,EAAE,MAAM,KAAK,EAAE,IAAI,UAAU,EAAE,YAAY,KAAK,CAAC,CAAC;AAC5M,YAAM,OAAO,MAAM,GAChB,KAAK,MAAM,EACX,OAAO,gBAAgB,EACvB,IAAI,EAAE,QAAQ,YAAY,KAAK,UAAU,GAAG,UAAU,MAAM,iBAAiB,QAAQ,OAAO,CAAC,EAC7F,MAAM,QAAQ,iBAAiB,KAAK,GAAG,OAAO,EAAE,KAAK,OAAO,IAAI,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC,EAChF,UAAU,EAAE,KAAK,iBAAiB,KAAK,QAAQ,iBAAiB,SAAS,YAAY,iBAAiB,YAAY,UAAU,iBAAiB,SAAS,CAAC;AAC1J,aAAO,KACJ,KAAK,CAAC,MAAM,UAAU,KAAK,WAAW,QAAQ,IAAI,MAAM,WAAW,QAAQ,MAAM,KAAK,MAAM,MAAM,MAAM,KAAK,EAAE,EAC/G,IAAI,CAAC,EAAE,KAAK,QAAQ,YAAY,SAAS,OAAO,EAAE,KAAK,QAAQ,YAAY,WAAW,YAAY,GAAG,SAAS,EAAE;AAAA,IACrH;AAAA,IACA,MAAM,WAAW,QAAQ;AACvB,YAAM,GACH,OAAO,gBAAgB,EACvB,OAAO,EAAE,IAAI,OAAO,IAAI,WAAW,OAAO,WAAW,OAAO,OAAO,OAAO,gBAAgB,OAAO,gBAAgB,QAAQ,WAAW,OAAO,MAAM,GAAG,WAAW,KAAK,OAAO,SAAS,EAAE,CAAC;AAAA,IAC5L;AAAA,IACA,MAAM,WAAW,KAAK,IAAI,KAAK;AAC7B,YAAM,CAAC,GAAG,IAAI,MAAM,GACjB,OAAO,gBAAgB,EACvB,IAAI,EAAE,QAAQ,KAAK,GAAG,EAAE,CAAC,EACzB;AAAA,QACC;AAAA,UACE,GAAG,iBAAiB,IAAI,EAAE;AAAA,UAC1B,GAAG,iBAAiB,WAAW,IAAI,SAAS;AAAA,UAC5C,GAAG,iBAAiB,OAAO,IAAI,KAAK;AAAA,UACpC,GAAG,iBAAiB,gBAAgB,IAAI,cAAc;AAAA,UACtD,OAAO,iBAAiB,MAAM;AAAA,QAChC;AAAA,MACF,EACC,UAAU,EAAE,QAAQ,iBAAiB,OAAO,CAAC;AAChD,aAAO,MAAO,IAAI,SAA0B;AAAA,IAC9C;AAAA,IACA,MAAM,oBAAoB,UAAU,QAAQ,YAAY;AACtD,YAAM,EAAE,IAAI,WAAW,OAAO,gBAAgB,WAAW,GAAG,QAAQ,IAAI;AACxE,YAAM,SAAS,EAAE,IAAI,WAAW,OAAO,gBAAgB,SAAS,WAAW,OAAO,GAAG,QAAQ,YAAY,KAAK,UAAU,GAAG,WAAW,KAAK,SAAS,EAAE;AACtJ,YAAM,GAAG,OAAO,mBAAmB,EAAE,OAAO,MAAM,EAAE,mBAAmB,EAAE,QAAQ,oBAAoB,IAAI,KAAK,OAAO,CAAC;AAAA,IACxH;AAAA,IACA,MAAM,sBAAsB,IAAI;AAC9B,YAAM,GAAG,OAAO,mBAAmB,EAAE,MAAM,GAAG,oBAAoB,IAAI,EAAE,CAAC;AAAA,IAC3E;AAAA,IACA,MAAM,uBAAuB,QAAQ,YAAY;AAC/C,YAAM,GAAG,OAAO,mBAAmB,EAAE,IAAI,EAAE,YAAY,KAAK,UAAU,EAAE,CAAC,EAAE,MAAM,GAAG,oBAAoB,QAAQ,MAAM,CAAC;AAAA,IACzH;AAAA,IACA,MAAM,wBAAwB,KAAK,QAAQ,KAAK,YAAY,OAAO;AACjE,YAAM,YAAY,IAAI,GAAG,oBAAoB,WAAW,IAAI,SAAS,GAAG,GAAG,oBAAoB,OAAO,IAAI,KAAK,GAAG,IAAI,oBAAoB,YAAY,KAAK,GAAG,CAAC,CAAC;AAGhK,YAAM,SAAS,GACZ,MAAM,QAAQ,EACd,GAAG,GAAG,OAAO,EAAE,IAAI,oBAAoB,GAAG,CAAC,EAAE,KAAK,mBAAmB,EAAE,MAAM,SAAS,EAAE,QAAQ,IAAI,oBAAoB,GAAG,CAAC,EAAE,MAAM,KAAK,EAAE,IAAI,UAAU,EAAE,YAAY,KAAK,CAAC,CAAC;AACjL,YAAM,OAAO,MAAM,GAChB,KAAK,MAAM,EACX,OAAO,mBAAmB,EAC1B,IAAI,EAAE,QAAQ,YAAY,KAAK,UAAU,EAAE,CAAC,EAC5C,MAAM,QAAQ,oBAAoB,IAAI,GAAG,OAAO,EAAE,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC,EAChF,UAAU;AACb,aAAO,KAAK,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,MAAM,GAAG,EAAE,IAAI,UAAU;AAAA,IACxE;AAAA,IACA,MAAM,aAAa,MAAM,QAAQ,YAAY,KAAK;AAChD,YAAM,OAAO,MAAM,GAChB,OAAO,eAAe,EACtB,OAAO,EAAE,MAAM,QAAQ,YAAY,KAAK,UAAU,EAAE,CAAC,EACrD,mBAAmB;AAAA,QAClB,QAAQ,gBAAgB;AAAA,QACxB,KAAK,EAAE,QAAQ,YAAY,KAAK,UAAU,EAAE;AAAA,QAC5C,UAAU,GAAG,GAAG,gBAAgB,QAAQ,MAAM,GAAG,IAAI,gBAAgB,YAAY,KAAK,GAAG,CAAC,CAAC;AAAA,MAC7F,CAAC,EACA,UAAU,EAAE,MAAM,gBAAgB,KAAK,CAAC;AAC3C,aAAO,KAAK,WAAW;AAAA,IACzB;AAAA,IACA,MAAM,aAAa,MAAM,QAAQ;AAC/B,YAAM,GAAG,OAAO,eAAe,EAAE,MAAM,IAAI,GAAG,gBAAgB,MAAM,IAAI,GAAG,GAAG,gBAAgB,QAAQ,MAAM,CAAC,CAAC;AAAA,IAChH;AAAA,IACA,MAAM,YAAY,QAAQ;AACxB,YAAM,SAAS,KAAK,MAAM;AAC1B,YAAM,GAAG,YAAY,OAAO,OAAO;AACjC,cAAM,GAAG,OAAO,kBAAkB,EAAE,MAAM,GAAG,mBAAmB,WAAW,MAAM,CAAC;AAGlF,cAAM,GAAG,OAAO,gBAAgB,EAAE,MAAM,IAAI,GAAG,iBAAiB,YAAY,MAAM,GAAG,GAAG,OAAO,iBAAiB,MAAM,GAAG,GAAG,iBAAiB,YAAY,MAAM,CAAC,CAAC,CAAC;AAClK,cAAM,GAAG,OAAO,gBAAgB,EAAE,MAAM,GAAG,iBAAiB,WAAW,MAAM,CAAC;AAC9E,cAAM,GAAG,OAAO,mBAAmB,EAAE,MAAM,GAAG,oBAAoB,IAAI,MAAM,CAAC;AAAA,MAC/E,CAAC;AAAA,IACH;AAAA,IACA,QAAQ;AACN,kBAAY,KAAK,IAAI;AACrB,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEA,SAAS,OAAO,KAAwD;AACtE,QAAM,EAAE,UAAU,UAAU,GAAG,KAAK,IAAI;AACxC,SAAO,EAAE,GAAG,MAAM,GAAI,aAAa,QAAQ,EAAE,SAAS,GAAI,UAAU,SAAS,YAAY,EAAE;AAC7F;AAEA,SAAS,eAAe,KAAwE;AAC9F,QAAM,EAAE,aAAa,eAAe,WAAW,WAAW,GAAG,KAAK,IAAI;AACtE,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAI,gBAAgB,QAAQ,EAAE,YAAY;AAAA,IAC1C,GAAI,kBAAkB,QAAQ,EAAE,cAAc;AAAA,IAC9C,WAAW,UAAU,YAAY;AAAA,IACjC,WAAW,UAAU,YAAY;AAAA,EACnC;AACF;AAEA,SAAS,WAAW,KAA+D;AACjF,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,WAAW,IAAI;AAAA,IACf,OAAO,IAAI;AAAA,IACX,gBAAgB,IAAI;AAAA,IACpB,GAAI,IAAI;AAAA,IACR,WAAW,IAAI,UAAU,YAAY;AAAA,EACvC;AACF;;;ADtRA,IAAM,oBAAoB;AAC1B,IAAM,mBAAmB,cAAc,IAAI,IAAI,uBAAuB,YAAY,GAAG,CAAC;AAetF,eAAsBC,8BAA6B,SAAyE;AAC1H,QAAM,WAAW,QAAQ,aAAa,CAAC,YAAoB,QAAQ,MAAM,OAAO;AAChF,QAAM,OAAO,IAAI,GAAG,KAAK,EAAE,kBAAkB,QAAQ,kBAAkB,KAAK,QAAQ,YAAY,kBAAkB,CAAC;AAEnH,OAAK,GAAG,SAAS,CAAC,UAAU,SAAS,oNAAoD,MAAM,OAAO,EAAE,CAAC;AACzG,MAAI;AACF,UAAM,0BAA0B,MAAM,gBAAgB;AAAA,EACxD,SAAS,OAAO;AACd,UAAM,KAAK,IAAI,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC/B,UAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACpE,UAAM,IAAI,MAAM,iRAA+D,OAAO,MAAM,QAAQ,gBAAgB,EAAE,KAAK,2GAAsB,CAAC,EAAE;AAAA,EACtJ;AACA,SAAO,6BAA2B,IAAI;AACxC;","names":["migrationsFolder","createPostgresMessengerStore"]}
@@ -1,8 +1,8 @@
1
1
  import type { Pool } from "pg";
2
2
  import type { ClosableMessengerStore } from "../types.js";
3
3
  /**
4
- * Приводит таблицы мессенджеров к последней миграции. Идёт на одном соединении под блокировкой базы:
5
- * если несколько серверов стартуют сразу, второй ждёт первого и видит применённые миграции.
4
+ * Приводит таблицы мессенджеров к последней миграции. Схема `agent_chat` создаётся под общей блокировкой всех пакетов, миграции — под своей:
5
+ * копии пакета ждут друг друга, а разные пакеты стартуют разом на пустой базе без гонки за схему.
6
6
  */
7
7
  export declare function migrateMessengersToLatest(pool: Pool, migrationsFolder: string): Promise<void>;
8
8
  /** Хранилище мессенджеров в PostgreSQL (схема `agent_chat`). Одноразовое берётся одним условным запросом. Закрытие закрывает пул, повторное — ничего не делает. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ykurilov/agent-chat-messengers",
3
- "version": "0.56.0",
3
+ "version": "0.58.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -61,13 +61,13 @@
61
61
  },
62
62
  "dependencies": {
63
63
  "drizzle-orm": "^0.45.3",
64
- "@ykurilov/agent-chat-core": "0.56.0"
64
+ "@ykurilov/agent-chat-core": "0.58.0"
65
65
  },
66
66
  "peerDependencies": {
67
67
  "better-sqlite3": "^13.0.3",
68
68
  "pg": "^8.23.0",
69
69
  "react": "^19.3.0",
70
- "@ykurilov/agent-chat-react": "^0.56.0"
70
+ "@ykurilov/agent-chat-react": "^0.58.0"
71
71
  },
72
72
  "peerDependenciesMeta": {
73
73
  "better-sqlite3": {
@@ -98,12 +98,12 @@
98
98
  "pg": "^8.23.0",
99
99
  "react": "^19.3.0",
100
100
  "react-dom": "^19.3.0",
101
- "@ykurilov/agent-chat-attachments": "0.56.0",
102
- "@ykurilov/agent-chat-background": "0.56.0",
103
- "@ykurilov/agent-chat-limits": "0.56.0",
104
- "@ykurilov/agent-chat-react": "0.56.0",
105
- "@ykurilov/agent-chat-voice": "0.56.0",
106
- "@ykurilov/agent-chat-server": "0.56.0"
101
+ "@ykurilov/agent-chat-attachments": "0.58.0",
102
+ "@ykurilov/agent-chat-background": "0.58.0",
103
+ "@ykurilov/agent-chat-limits": "0.58.0",
104
+ "@ykurilov/agent-chat-react": "0.58.0",
105
+ "@ykurilov/agent-chat-voice": "0.58.0",
106
+ "@ykurilov/agent-chat-server": "0.58.0"
107
107
  },
108
108
  "scripts": {
109
109
  "build": "node ../../scripts/build-package.mjs",