@ykurilov/agent-chat-flows 0.80.0 → 0.81.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/dist/postgres.js CHANGED
@@ -3,11 +3,13 @@ import {
3
3
  FLOWS_MIGRATION_LOCK_KEY
4
4
  } from "./chunk-BNMQUNSI.js";
5
5
  import {
6
+ FLOW_DELIVERY_EXIT,
6
7
  byVersionAndState,
7
8
  compareText,
8
9
  migratePending,
10
+ normalizedDelivery,
9
11
  withoutNul
10
- } from "./chunk-DCTRMCGV.js";
12
+ } from "./chunk-DOKHNTPU.js";
11
13
 
12
14
  // src/postgres.ts
13
15
  import { fileURLToPath } from "url";
@@ -148,12 +150,23 @@ function mappedStateSql(map) {
148
150
  const cases = entries.map(([from, to]) => sql`when ${from} then ${to}`);
149
151
  return sql`case ${flowChats.stateId} ${sql.join(cases, sql` `)} else ${flowChats.stateId} end`;
150
152
  }
153
+ var leaseFree = (now) => or(isNull(flowChats.leaseUntil), lt(flowChats.leaseUntil, now));
151
154
  var dueAndFree = (now) => and(
152
155
  lte(flowChats.dueAt, now),
153
- or(isNull(flowChats.leaseUntil), lt(flowChats.leaseUntil, now)),
154
- isNull(flowChats.endedAt),
156
+ leaseFree(now),
157
+ or(isNull(flowChats.endedAt), eq(flowChats.dueExitId, FLOW_DELIVERY_EXIT)),
155
158
  or(isNull(flowChats.pausedUntil), lte(flowChats.pausedUntil, now))
156
159
  );
160
+ function deliveryIs(expected) {
161
+ if (expected === null) return isNull(flowChats.delivery);
162
+ const clean = withoutNul(expected);
163
+ return and(
164
+ sql`${flowChats.delivery}->>'messageId' = ${clean.messageId}`,
165
+ sql`(${flowChats.delivery}->>'attempt')::integer = ${clean.attempt}`,
166
+ sql`${flowChats.delivery}->>'token' is not distinct from ${clean.token ?? null}::text`
167
+ );
168
+ }
169
+ var unlessDeliveryWaits = (column) => sql`case when ${flowChats.delivery} is null then null else ${column} end`;
157
170
  var inCodeOrder = (column) => sql`${column} collate "C"`;
158
171
  async function migrateFlowsToLatest(pool, migrationsFolder) {
159
172
  await migratePostgresSchema({
@@ -173,6 +186,7 @@ function createPostgresFlowStore(pool, migrationsFolder) {
173
186
  return row;
174
187
  }
175
188
  return {
189
+ guarded: true,
176
190
  async pinChat(input) {
177
191
  await ready;
178
192
  const clean = withoutNul(input);
@@ -228,7 +242,13 @@ function createPostgresFlowStore(pool, migrationsFolder) {
228
242
  dueBranchHead: due ? withoutNul(due.branchHead) : null,
229
243
  leaseUntil: null,
230
244
  leaseOwner: null
231
- }).where(and(eq(flowChats.chatId, chatId), options?.unlessDelivery ? isNull(flowChats.delivery) : void 0));
245
+ }).where(
246
+ and(
247
+ eq(flowChats.chatId, chatId),
248
+ options?.unlessDelivery ? isNull(flowChats.delivery) : void 0,
249
+ options?.leaseOwner !== void 0 ? eq(flowChats.leaseOwner, withoutNul(options.leaseOwner)) : void 0
250
+ )
251
+ );
232
252
  },
233
253
  async leaseDue({ now, owner, leaseMs, limit }) {
234
254
  await ready;
@@ -324,7 +344,15 @@ function createPostgresFlowStore(pool, migrationsFolder) {
324
344
  },
325
345
  async endChat(chatId, { reason, at }) {
326
346
  await ready;
327
- await db.update(flowChats).set({ endedAt: new Date(at), endReason: reason, dueAt: null, dueExitId: null, dueBranchHead: null, leaseUntil: null, leaseOwner: null }).where(and(eq(flowChats.chatId, chatId), isNull(flowChats.endedAt)));
347
+ await db.update(flowChats).set({
348
+ endedAt: new Date(at),
349
+ endReason: reason,
350
+ dueAt: unlessDeliveryWaits(flowChats.dueAt),
351
+ dueExitId: unlessDeliveryWaits(flowChats.dueExitId),
352
+ dueBranchHead: unlessDeliveryWaits(flowChats.dueBranchHead),
353
+ leaseUntil: unlessDeliveryWaits(flowChats.leaseUntil),
354
+ leaseOwner: unlessDeliveryWaits(flowChats.leaseOwner)
355
+ }).where(and(eq(flowChats.chatId, chatId), isNull(flowChats.endedAt)));
328
356
  const row = await getChat(chatId);
329
357
  return row ? toRecord(row) : null;
330
358
  },
@@ -348,8 +376,29 @@ function createPostgresFlowStore(pool, migrationsFolder) {
348
376
  },
349
377
  async setDelivery(chatId, delivery) {
350
378
  await ready;
351
- const clean = delivery === null ? null : withoutNul({ ...delivery, retryAt: toIso(new Date(delivery.retryAt)) });
352
- await db.update(flowChats).set({ delivery: clean }).where(eq(flowChats.chatId, chatId));
379
+ await db.update(flowChats).set({ delivery: delivery && withoutNul(normalizedDelivery(delivery)) }).where(eq(flowChats.chatId, chatId));
380
+ },
381
+ async swapDelivery(chatId, { expected, next, due, claim, freeAt, endedAt }) {
382
+ await ready;
383
+ const claimAt = claim && new Date(claim.now);
384
+ const swapped = await db.update(flowChats).set({
385
+ delivery: next && withoutNul(normalizedDelivery(next)),
386
+ ...due !== void 0 && {
387
+ dueAt: due ? new Date(due.at) : null,
388
+ dueExitId: due ? withoutNul(due.exitId) : null,
389
+ dueBranchHead: due ? withoutNul(due.branchHead) : null
390
+ },
391
+ ...claim && { leaseOwner: withoutNul(claim.owner), leaseUntil: new Date(claimAt.getTime() + claim.leaseMs) }
392
+ }).where(
393
+ and(
394
+ eq(flowChats.chatId, chatId),
395
+ deliveryIs(expected),
396
+ claim ? or(leaseFree(claimAt), eq(flowChats.leaseOwner, withoutNul(claim.owner)), claim.from !== void 0 ? eq(flowChats.leaseOwner, withoutNul(claim.from)) : void 0) : void 0,
397
+ freeAt !== void 0 ? leaseFree(new Date(freeAt)) : void 0,
398
+ endedAt === void 0 ? void 0 : endedAt === null ? isNull(flowChats.endedAt) : eq(flowChats.endedAt, new Date(endedAt))
399
+ )
400
+ ).returning({ chatId: flowChats.chatId });
401
+ return swapped.length > 0;
353
402
  },
354
403
  async readTimeZone(ownerId) {
355
404
  await ready;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/postgres.ts","../src/store/postgres.ts","../src/store/postgres-schema.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport type { Pool } from \"pg\";\nimport { createPostgresFlowStore, type PostgresFlowStore } from \"./store/postgres\";\n\nexport type { PostgresFlowStore } from \"./store/postgres\";\nexport { FLOWS_MIGRATIONS_TABLE, FLOWS_MIGRATION_LOCK_KEY } from \"./store/migration-settings\";\nexport { flowChats, flowClientCards, flowFirstMessages, flowVersions } from \"./store/postgres-schema\";\n\n/** Миграции лежат в пакете рядом с `dist`: `drizzle/postgres`. */\nconst MIGRATIONS_FOLDER = fileURLToPath(new URL(\"../drizzle/postgres\", import.meta.url));\n\n/**\n * Сценарии в PostgreSQL на пуле приложения (схема `agent_chat`, та же база, что у хранилища чатов).\n * Таблицы приводятся к последней миграции при первом обращении. Пул закрывает приложение.\n */\nexport function postgresFlowStore(pool: Pool): PostgresFlowStore {\n return createPostgresFlowStore(pool, MIGRATIONS_FOLDER);\n}\n","import { and, eq, inArray, isNull, lt, lte, ne, or, sql, type 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 { StateMap } from \"../engine/migrate-run\";\nimport type { FlowChatRecord, FlowStore, PendingFlowChange, SideTaskRecord } from \"./flow-store\";\nimport { byVersionAndState, compareText, migratePending, withoutNul } from \"./flow-store-shared\";\nimport { FLOWS_MIGRATION_LOCK_KEY, FLOWS_MIGRATIONS_TABLE } from \"./migration-settings\";\nimport { flowChats, flowClientCards, flowFirstMessages, flowPeople, flowSideTasks, flowVersions } from \"./postgres-schema\";\n\n/** Хранилище, у которого можно дождаться миграции перед закрытием пула приложения. */\nexport type PostgresFlowStore = FlowStore & Required<Pick<FlowStore, \"close\">>;\n\ntype ChatRow = typeof flowChats.$inferSelect;\n\nconst toIso = (date: Date) => date.toISOString();\n\nfunction toRecord(row: ChatRow): FlowChatRecord {\n return {\n chatId: row.chatId,\n ownerId: row.ownerId,\n flowId: row.flowId,\n versionId: row.versionId,\n stateId: row.stateId,\n pending: row.pending,\n dueAt: row.dueAt === null ? null : toIso(row.dueAt),\n dueExitId: row.dueExitId,\n dueBranchHead: row.dueBranchHead,\n timeZone: row.timeZone,\n updatedAt: toIso(row.updatedAt),\n endedAt: row.endedAt === null ? null : toIso(row.endedAt),\n endReason: row.endReason,\n reopenedAt: row.reopenedAt === null ? null : toIso(row.reopenedAt),\n pausedUntil: row.pausedUntil === null ? null : toIso(row.pausedUntil),\n windowClosed: row.windowClosed,\n dayClosedOn: row.dayClosedOn,\n delivery: row.delivery,\n placement: row.placement,\n };\n}\n\ntype SideTaskRow = typeof flowSideTasks.$inferSelect;\nconst toSideTask = (row: SideTaskRow): SideTaskRecord => ({\n taskId: row.taskId,\n chatId: row.chatId === \"\" ? null : row.chatId,\n asks: row.asks,\n doneAt: row.doneAt === null ? null : toIso(row.doneAt),\n});\n\n/** JSON параметром запроса: текст, приведённый к `jsonb` в самой базе. */\nconst jsonb = (value: unknown): SQL => sql`${JSON.stringify(value)}::jsonb`;\n\n/**\n * Дописать изменение в конец `pending`, если такого ключа там ещё нет. Условие — в том же `update`: вторая копия ждёт\n * запись и перепроверяет его уже на новой версии строки.\n */\nconst pendingWith = (change: PendingFlowChange): SQL =>\n sql`case when ${flowChats.pending} @> ${jsonb([{ key: change.key }])} then ${keptWith(change)}\n else ${flowChats.pending} || ${jsonb([change])} end`;\n\n/** Тот же ключ: прежнее остаётся, но `appliedAt` перехода дописывается к ожидающему без него (как `pendingAdded`). */\nfunction keptWith(change: PendingFlowChange): SQL {\n if (change.kind !== \"transition\" || change.appliedAt === undefined) return sql`${flowChats.pending}`;\n return sql`(select jsonb_agg(case when item->>'key' = ${change.key} and item->'appliedAt' is null and item->>'kind' = 'transition'\n then item || jsonb_build_object('appliedAt', ${change.appliedAt}::text) else item end order by place)\n from jsonb_array_elements(${flowChats.pending}) with ordinality as list(item, place))`;\n}\n\n/** Состояние по карте перевода: удалённые — в выбранные, остальные как были. */\nfunction mappedStateSql(map: StateMap): SQL {\n const entries = Object.entries(map);\n if (entries.length === 0) return sql`${flowChats.stateId}`;\n const cases = entries.map(([from, to]) => sql`when ${from} then ${to}`);\n return sql`case ${flowChats.stateId} ${sql.join(cases, sql` `)} else ${flowChats.stateId} end`;\n}\n\n/** Таймер наступил, никто его не держит, разговор идёт и не на паузе. Аренда до `lease_until` включительно ещё живая. */\nconst dueAndFree = (now: Date) =>\n and(\n lte(flowChats.dueAt, now),\n or(isNull(flowChats.leaseUntil), lt(flowChats.leaseUntil, now)),\n isNull(flowChats.endedAt),\n or(isNull(flowChats.pausedUntil), lte(flowChats.pausedUntil, now)),\n );\n\n/** Порядок по кодам символов, как у SQLite и памяти, а не по языку базы. */\nconst inCodeOrder = (column: typeof flowChats.chatId) => sql`${column} collate \"C\"`;\n\n/**\n * Приводит таблицы сценариев к последней миграции. Схема `agent_chat` создаётся под общей блокировкой всех пакетов, миграции — под своей:\n * копии пакета ждут друг друга, а разные пакеты стартуют разом на пустой базе без гонки за схему.\n */\nexport async function migrateFlowsToLatest(pool: Pool, migrationsFolder: string): Promise<void> {\n await migratePostgresSchema({\n connect: () => pool.connect(),\n lockKey: FLOWS_MIGRATION_LOCK_KEY,\n migrate: (client) => migrate(drizzle(client), { migrationsFolder, migrationsSchema: \"agent_chat\", migrationsTable: FLOWS_MIGRATIONS_TABLE }),\n });\n}\n\n/**\n * Сценарии в PostgreSQL (схема `agent_chat`) на пуле приложения. Миграция идёт при первом обращении, каждая операция\n * её дожидается. Копии сервера делят таймеры и перевод чатов через `for update skip locked`: строку, которую уже\n * берёт другая копия, эта пропускает. Закрытие пул не закрывает: он принадлежит приложению.\n */\nexport function createPostgresFlowStore(pool: Pool, migrationsFolder: string): PostgresFlowStore {\n const db = drizzle(pool);\n const ready = migrateFlowsToLatest(pool, migrationsFolder);\n // Ошибка миграции придёт в каждую операцию; без этого она всплыла бы ещё и как необработанная.\n ready.catch(() => {});\n\n async function getChat(chatId: string): Promise<ChatRow | undefined> {\n await ready;\n const [row] = await db.select().from(flowChats).where(eq(flowChats.chatId, chatId));\n return row;\n }\n\n return {\n async pinChat(input) {\n await ready;\n const clean = withoutNul(input);\n await db\n .insert(flowChats)\n .values({\n chatId: clean.chatId,\n ownerId: clean.ownerId,\n flowId: clean.flowId,\n versionId: clean.versionId,\n stateId: clean.stateId,\n pending: [],\n timeZone: clean.timeZone,\n ...(clean.placement && { placement: clean.placement }),\n updatedAt: new Date(clean.at),\n })\n .onConflictDoNothing();\n return toRecord((await getChat(clean.chatId))!);\n },\n async getChat(chatId) {\n const row = await getChat(chatId);\n return row ? toRecord(row) : null;\n },\n async updateChat(chatId, patch, at) {\n await ready;\n const clean = withoutNul(patch);\n const [row] = await db\n .update(flowChats)\n .set({ versionId: clean.versionId, stateId: clean.stateId, timeZone: clean.timeZone, placement: clean.placement, updatedAt: new Date(at) })\n .where(eq(flowChats.chatId, chatId))\n .returning();\n return row ? toRecord(row) : null;\n },\n async deleteChat(chatId) {\n await ready;\n await db.delete(flowChats).where(eq(flowChats.chatId, chatId));\n },\n\n async addPending(chatId, change) {\n await ready;\n await db.update(flowChats).set({ pending: pendingWith(withoutNul(change)) }).where(eq(flowChats.chatId, chatId));\n },\n async takePending(chatId) {\n await ready;\n // Старое значение — из строки, захваченной `for update`: вторая копия ждёт её и получает уже пустой список\n // (кроме переходов, применённых в срок: они остаются).\n const result = await db.execute<{ pending: PendingFlowChange[] }>(sql`\n with taken as (select chat_id, pending from ${flowChats} where chat_id = ${chatId} for update)\n update ${flowChats} set pending = (\n select coalesce(jsonb_agg(item order by place), '[]'::jsonb)\n from jsonb_array_elements(taken.pending) with ordinality as list(item, place)\n where item->>'kind' = 'transition' and item->'appliedAt' is not null\n ) from taken\n where ${flowChats.chatId} = taken.chat_id\n returning taken.pending`);\n return result.rows[0]?.pending ?? [];\n },\n\n async setDue(chatId, due, options) {\n await ready;\n await db\n .update(flowChats)\n .set({\n dueAt: due ? new Date(due.at) : null,\n dueExitId: due ? withoutNul(due.exitId) : null,\n dueBranchHead: due ? withoutNul(due.branchHead) : null,\n leaseUntil: null,\n leaseOwner: null,\n })\n .where(and(eq(flowChats.chatId, chatId), options?.unlessDelivery ? isNull(flowChats.delivery) : undefined));\n },\n async leaseDue({ now, owner, leaseMs, limit }) {\n await ready;\n const nowDate = new Date(now);\n // Строки, которые сейчас берёт другая копия, пропускаются; условие повторено снаружи — его база перепроверит\n // на новой версии строки, если та успела измениться.\n // Выборка — отдельным шагом `with`: подзапрос с пределом внутри `in (…)` база может перечитать и взять больше.\n const due = db.$with(\"due\").as(\n db\n .select({ chatId: flowChats.chatId })\n .from(flowChats)\n .where(dueAndFree(nowDate))\n .orderBy(flowChats.dueAt, inCodeOrder(flowChats.chatId))\n .limit(limit)\n .for(\"update\", { skipLocked: true }),\n );\n const rows = await db\n .with(due)\n .update(flowChats)\n .set({ leaseOwner: withoutNul(owner), leaseUntil: new Date(nowDate.getTime() + leaseMs) })\n .where(and(inArray(flowChats.chatId, db.select({ chatId: due.chatId }).from(due)), dueAndFree(nowDate)))\n .returning();\n return rows.map(toRecord).sort((left, right) => compareText(left.dueAt!, right.dueAt!) || compareText(left.chatId, right.chatId));\n },\n async releaseLease(chatId, owner) {\n await ready;\n await db\n .update(flowChats)\n .set({ leaseOwner: null, leaseUntil: null })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.leaseOwner, owner)));\n },\n async renewLease({ chatId, owner, now, leaseMs }) {\n await ready;\n const renewed = await db\n .update(flowChats)\n .set({ leaseUntil: new Date(new Date(now).getTime() + leaseMs) })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.leaseOwner, withoutNul(owner))))\n .returning({ chatId: flowChats.chatId });\n return renewed.length > 0;\n },\n\n async saveVersion(input) {\n await ready;\n const clean = withoutNul(input);\n const inserted = await db\n .insert(flowVersions)\n .values({ flowId: clean.flowId, versionId: clean.versionId, hash: clean.hash, flow: clean.flow, createdAt: new Date(clean.at) })\n .onConflictDoNothing()\n .returning({ hash: flowVersions.hash });\n if (inserted.length > 0) return \"new\";\n const [saved] = await db\n .select({ hash: flowVersions.hash })\n .from(flowVersions)\n .where(and(eq(flowVersions.flowId, clean.flowId), eq(flowVersions.versionId, clean.versionId)));\n return saved?.hash === clean.hash ? \"same\" : \"conflict\";\n },\n async getVersion(flowId, versionId) {\n await ready;\n const [row] = await db\n .select()\n .from(flowVersions)\n .where(and(eq(flowVersions.flowId, flowId), eq(flowVersions.versionId, versionId)));\n return row ? { ...row, createdAt: toIso(row.createdAt) } : null;\n },\n\n async countChatsByState({ flowId }) {\n await ready;\n const rows = await db\n .select({ versionId: flowChats.versionId, stateId: flowChats.stateId, chats: sql<number>`count(*)::int` })\n .from(flowChats)\n .where(eq(flowChats.flowId, flowId))\n .groupBy(flowChats.versionId, flowChats.stateId);\n return rows.map((row) => ({ ...row, chats: Number(row.chats) })).sort(byVersionAndState);\n },\n async listFlowChats(input) {\n await ready;\n const { flowId, exceptVersionId, chatIds } = withoutNul(input);\n if (chatIds?.length === 0) return [];\n const rows = await db\n .select({\n chatId: flowChats.chatId,\n ownerId: flowChats.ownerId,\n flowId: flowChats.flowId,\n versionId: flowChats.versionId,\n stateId: flowChats.stateId,\n endedAt: flowChats.endedAt,\n endReason: flowChats.endReason,\n })\n .from(flowChats)\n .where(\n and(\n eq(flowChats.flowId, flowId),\n exceptVersionId === undefined ? undefined : ne(flowChats.versionId, exceptVersionId),\n chatIds === undefined ? undefined : inArray(flowChats.chatId, chatIds),\n ),\n )\n .orderBy(inCodeOrder(flowChats.chatId));\n return rows.map((row) => ({ ...row, endedAt: row.endedAt === null ? null : toIso(row.endedAt) }));\n },\n async migrateBatch(input) {\n await ready;\n const clean = withoutNul(input);\n const change = migratePending(clean);\n if (clean.chatIds?.length === 0) return { moved: [] };\n const notYetMoved = and(\n eq(flowChats.flowId, clean.flowId),\n ne(flowChats.versionId, clean.toVersionId),\n clean.chatIds === undefined ? undefined : inArray(flowChats.chatId, clean.chatIds),\n );\n const batch = db\n .select({ chatId: flowChats.chatId })\n .from(flowChats)\n .where(notYetMoved)\n .orderBy(inCodeOrder(flowChats.chatId))\n .limit(clean.limit)\n .for(\"update\", { skipLocked: true });\n const moved = await db\n .update(flowChats)\n .set({ versionId: clean.toVersionId, stateId: mappedStateSql(clean.map), pending: pendingWith(change), updatedAt: new Date(clean.at) })\n .where(and(inArray(flowChats.chatId, batch), notYetMoved))\n .returning({ chatId: flowChats.chatId });\n return { moved: moved.map((row) => row.chatId) };\n },\n\n async readCard(ownerId) {\n await ready;\n // Значение читаем текстом и разбираем сами: иначе строка «007» из jsonb вернулась бы числом 7.\n const rows = await db\n .select({\n fieldId: flowClientCards.fieldId,\n value: sql<string>`${flowClientCards.value}::text`,\n updatedAt: flowClientCards.updatedAt,\n chatId: flowClientCards.chatId,\n flowId: flowClientCards.flowId,\n })\n .from(flowClientCards)\n .where(eq(flowClientCards.ownerId, ownerId));\n return rows\n .map((row) => ({ ...row, value: JSON.parse(row.value) as unknown, updatedAt: toIso(row.updatedAt) }))\n .sort((left, right) => compareText(left.fieldId, right.fieldId));\n },\n async writeCardField(input) {\n await ready;\n const clean = withoutNul(input);\n const entry = { value: jsonb(clean.value), updatedAt: new Date(clean.at), chatId: clean.chatId, flowId: clean.flowId };\n await db\n .insert(flowClientCards)\n .values({ ownerId: clean.ownerId, fieldId: clean.fieldId, ...entry })\n .onConflictDoUpdate({ target: [flowClientCards.ownerId, flowClientCards.fieldId], set: entry });\n },\n async forgetCard(ownerId, fieldId) {\n await ready;\n await db\n .delete(flowClientCards)\n .where(and(eq(flowClientCards.ownerId, ownerId), fieldId === \"all\" ? undefined : eq(flowClientCards.fieldId, fieldId)));\n },\n\n async endChat(chatId, { reason, at }) {\n await ready;\n await db\n .update(flowChats)\n .set({ endedAt: new Date(at), endReason: reason, dueAt: null, dueExitId: null, dueBranchHead: null, leaseUntil: null, leaseOwner: null })\n .where(and(eq(flowChats.chatId, chatId), isNull(flowChats.endedAt)));\n const row = await getChat(chatId);\n return row ? toRecord(row) : null;\n },\n async reopenChat(chatId, { endedAt, at }) {\n await ready;\n const [row] = await db\n .update(flowChats)\n .set({ endedAt: null, endReason: null, reopenedAt: new Date(at) })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.endedAt, new Date(endedAt))))\n .returning();\n return row ? toRecord(row) : null;\n },\n async setPause(chatId, until) {\n await ready;\n const [row] = await db\n .update(flowChats)\n .set({ pausedUntil: until === null ? null : new Date(until) })\n .where(eq(flowChats.chatId, chatId))\n .returning();\n return row ? toRecord(row) : null;\n },\n async setWindowClosed(chatId, closed) {\n await ready;\n await db.update(flowChats).set({ windowClosed: closed }).where(eq(flowChats.chatId, chatId));\n },\n async setDayClosed(chatId, day) {\n await ready;\n await db.update(flowChats).set({ dayClosedOn: day === null ? null : withoutNul(day) }).where(eq(flowChats.chatId, chatId));\n },\n async setDelivery(chatId, delivery) {\n await ready;\n const clean = delivery === null ? null : withoutNul({ ...delivery, retryAt: toIso(new Date(delivery.retryAt)) });\n await db.update(flowChats).set({ delivery: clean }).where(eq(flowChats.chatId, chatId));\n },\n\n async readTimeZone(ownerId) {\n await ready;\n const [row] = await db.select({ timeZone: flowPeople.timeZone }).from(flowPeople).where(eq(flowPeople.ownerId, ownerId));\n return row?.timeZone ?? null;\n },\n async writeTimeZone(ownerId, timeZone, at) {\n await ready;\n const clean = withoutNul({ ownerId, timeZone });\n await db\n .insert(flowPeople)\n .values({ ...clean, updatedAt: new Date(at) })\n .onConflictDoUpdate({ target: flowPeople.ownerId, set: { timeZone: clean.timeZone, updatedAt: new Date(at) } });\n },\n\n async forgetSideTasks(ownerId) {\n await ready;\n await db.delete(flowSideTasks).where(eq(flowSideTasks.ownerId, ownerId));\n },\n async readSideTasks(ownerId, chatId) {\n await ready;\n const rows = await db\n .select()\n .from(flowSideTasks)\n .where(and(eq(flowSideTasks.ownerId, ownerId), or(eq(flowSideTasks.chatId, \"\"), eq(flowSideTasks.chatId, chatId))));\n return rows.map(toSideTask).sort((left, right) => compareText(left.taskId, right.taskId));\n },\n async noteSideTask(input) {\n await ready;\n const clean = withoutNul(input);\n const asked = clean.status === \"asked\";\n const doneAt = asked ? null : new Date(clean.at);\n const [row] = await db\n .insert(flowSideTasks)\n .values({ ownerId: clean.ownerId, taskId: clean.taskId, chatId: clean.chatId ?? \"\", asks: asked ? 1 : 0, doneAt })\n .onConflictDoUpdate({\n target: [flowSideTasks.ownerId, flowSideTasks.taskId, flowSideTasks.chatId],\n set: asked ? { asks: sql`${flowSideTasks.asks} + 1` } : { doneAt: sql`coalesce(${flowSideTasks.doneAt}, excluded.done_at)` },\n })\n .returning();\n return toSideTask(row!);\n },\n\n async countFirstMessages(ownerId, day) {\n await ready;\n const [row] = await db\n .select({ count: flowFirstMessages.count })\n .from(flowFirstMessages)\n .where(and(eq(flowFirstMessages.ownerId, ownerId), eq(flowFirstMessages.day, day)));\n return row?.count ?? 0;\n },\n async addFirstMessage(ownerId, day) {\n await ready;\n const [row] = await db\n .insert(flowFirstMessages)\n .values({ ownerId: withoutNul(ownerId), day, count: 1 })\n .onConflictDoUpdate({ target: [flowFirstMessages.ownerId, flowFirstMessages.day], set: { count: sql`${flowFirstMessages.count} + 1` } })\n .returning({ count: flowFirstMessages.count });\n return row!.count;\n },\n\n async close() {\n // Дожидаемся миграции: пул приложения можно закрывать сразу после.\n await ready.catch(() => {});\n },\n };\n}\n","import type { AgentFlow } from \"../format/flow-format\";\nimport type { FlowEndReason } from \"../engine/flow-run\";\nimport type { FlowChatPlacement, FlowPendingDelivery, PendingFlowChange } from \"./flow-store\";\nimport { 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 });\n\n/** Чаты, которые идут по сценарию. */\nexport const flowChats = agentChatSchema.table(\n \"flow_chats\",\n {\n chatId: text(\"chat_id\").primaryKey(),\n ownerId: text(\"owner_id\").notNull(),\n flowId: text(\"flow_id\").notNull(),\n versionId: text(\"version_id\").notNull(),\n stateId: text(\"state_id\").notNull(),\n pending: jsonb(\"pending\").$type<PendingFlowChange[]>().notNull().default([]),\n dueAt: moment(\"due_at\"),\n dueExitId: text(\"due_exit_id\"),\n dueBranchHead: text(\"due_branch_head\"),\n timeZone: text(\"time_zone\").notNull(),\n leaseUntil: moment(\"lease_until\"),\n leaseOwner: text(\"lease_owner\"),\n updatedAt: moment(\"updated_at\").notNull(),\n endedAt: moment(\"ended_at\"),\n endReason: text(\"end_reason\").$type<FlowEndReason>(),\n reopenedAt: moment(\"reopened_at\"),\n pausedUntil: moment(\"paused_until\"),\n windowClosed: boolean(\"window_closed\").notNull().default(false),\n dayClosedOn: text(\"day_closed_on\"),\n delivery: jsonb(\"delivery\").$type<FlowPendingDelivery>(),\n placement: jsonb(\"placement\").$type<FlowChatPlacement>(),\n },\n (table) => [\n index(\"flow_chats_due_idx\").on(table.dueAt),\n index(\"flow_chats_flow_state_idx\").on(table.flowId, table.versionId, table.stateId),\n index(\"flow_chats_owner_idx\").on(table.ownerId),\n ],\n);\n\n/** Сохранённые версии сценариев: чат идёт по своей версии, пока его не перевели. */\nexport const flowVersions = agentChatSchema.table(\n \"flow_versions\",\n {\n flowId: text(\"flow_id\").notNull(),\n versionId: text(\"version_id\").notNull(),\n hash: text(\"hash\").notNull(),\n flow: jsonb(\"flow\").$type<AgentFlow>().notNull(),\n createdAt: moment(\"created_at\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.flowId, table.versionId] })],\n);\n\n/** Карточка клиента: поле за полем, переживает чаты. */\nexport const flowClientCards = agentChatSchema.table(\n \"flow_client_cards\",\n {\n ownerId: text(\"owner_id\").notNull(),\n fieldId: text(\"field_id\").notNull(),\n value: jsonb(\"value\").notNull(),\n updatedAt: moment(\"updated_at\").notNull(),\n chatId: text(\"chat_id\").notNull(),\n flowId: text(\"flow_id\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.fieldId] })],\n);\n\n/** Сколько сообщений агент написал первым за сутки человека. */\nexport const flowFirstMessages = agentChatSchema.table(\n \"flow_first_messages\",\n {\n ownerId: text(\"owner_id\").notNull(),\n day: text(\"day\").notNull(),\n count: integer(\"count\").notNull().default(0),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.day] })],\n);\n\n/** Пояс человека: общий для всех его чатов. */\nexport const flowPeople = agentChatSchema.table(\"flow_people\", {\n ownerId: text(\"owner_id\").primaryKey(),\n timeZone: text(\"time_zone\").notNull(),\n updatedAt: moment(\"updated_at\").notNull(),\n});\n\n/** Попытки попутных задач. `chat_id` пустой — попытки на человека, иначе — на разговор. */\nexport const flowSideTasks = agentChatSchema.table(\n \"flow_side_tasks\",\n {\n ownerId: text(\"owner_id\").notNull(),\n taskId: text(\"task_id\").notNull(),\n chatId: text(\"chat_id\").notNull().default(\"\"),\n asks: integer(\"asks\").notNull().default(0),\n doneAt: moment(\"done_at\"),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.taskId, table.chatId] })],\n);\n"],"mappings":"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","names":["jsonb"]}
1
+ {"version":3,"sources":["../src/postgres.ts","../src/store/postgres.ts","../src/store/postgres-schema.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport type { Pool } from \"pg\";\nimport { createPostgresFlowStore, type PostgresFlowStore } from \"./store/postgres\";\n\nexport type { PostgresFlowStore } from \"./store/postgres\";\nexport { FLOWS_MIGRATIONS_TABLE, FLOWS_MIGRATION_LOCK_KEY } from \"./store/migration-settings\";\nexport { flowChats, flowClientCards, flowFirstMessages, flowVersions } from \"./store/postgres-schema\";\n\n/** Миграции лежат в пакете рядом с `dist`: `drizzle/postgres`. */\nconst MIGRATIONS_FOLDER = fileURLToPath(new URL(\"../drizzle/postgres\", import.meta.url));\n\n/**\n * Сценарии в PostgreSQL на пуле приложения (схема `agent_chat`, та же база, что у хранилища чатов).\n * Таблицы приводятся к последней миграции при первом обращении. Пул закрывает приложение.\n */\nexport function postgresFlowStore(pool: Pool): PostgresFlowStore {\n return createPostgresFlowStore(pool, MIGRATIONS_FOLDER);\n}\n","import { and, eq, inArray, isNull, lt, lte, ne, or, sql, type AnyColumn, type 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 { StateMap } from \"../engine/migrate-run\";\nimport { FLOW_DELIVERY_EXIT, type FlowChatRecord, type FlowPendingDelivery, type FlowStore, type PendingFlowChange, type SideTaskRecord } from \"./flow-store\";\nimport { byVersionAndState, compareText, migratePending, normalizedDelivery, withoutNul } from \"./flow-store-shared\";\nimport { FLOWS_MIGRATION_LOCK_KEY, FLOWS_MIGRATIONS_TABLE } from \"./migration-settings\";\nimport { flowChats, flowClientCards, flowFirstMessages, flowPeople, flowSideTasks, flowVersions } from \"./postgres-schema\";\n\n/** Хранилище, у которого можно дождаться миграции перед закрытием пула приложения. */\nexport type PostgresFlowStore = FlowStore & Required<Pick<FlowStore, \"close\">>;\n\ntype ChatRow = typeof flowChats.$inferSelect;\n\nconst toIso = (date: Date) => date.toISOString();\n\nfunction toRecord(row: ChatRow): FlowChatRecord {\n return {\n chatId: row.chatId,\n ownerId: row.ownerId,\n flowId: row.flowId,\n versionId: row.versionId,\n stateId: row.stateId,\n pending: row.pending,\n dueAt: row.dueAt === null ? null : toIso(row.dueAt),\n dueExitId: row.dueExitId,\n dueBranchHead: row.dueBranchHead,\n timeZone: row.timeZone,\n updatedAt: toIso(row.updatedAt),\n endedAt: row.endedAt === null ? null : toIso(row.endedAt),\n endReason: row.endReason,\n reopenedAt: row.reopenedAt === null ? null : toIso(row.reopenedAt),\n pausedUntil: row.pausedUntil === null ? null : toIso(row.pausedUntil),\n windowClosed: row.windowClosed,\n dayClosedOn: row.dayClosedOn,\n delivery: row.delivery,\n placement: row.placement,\n };\n}\n\ntype SideTaskRow = typeof flowSideTasks.$inferSelect;\nconst toSideTask = (row: SideTaskRow): SideTaskRecord => ({\n taskId: row.taskId,\n chatId: row.chatId === \"\" ? null : row.chatId,\n asks: row.asks,\n doneAt: row.doneAt === null ? null : toIso(row.doneAt),\n});\n\n/** JSON параметром запроса: текст, приведённый к `jsonb` в самой базе. */\nconst jsonb = (value: unknown): SQL => sql`${JSON.stringify(value)}::jsonb`;\n\n/**\n * Дописать изменение в конец `pending`, если такого ключа там ещё нет. Условие — в том же `update`: вторая копия ждёт\n * запись и перепроверяет его уже на новой версии строки.\n */\nconst pendingWith = (change: PendingFlowChange): SQL =>\n sql`case when ${flowChats.pending} @> ${jsonb([{ key: change.key }])} then ${keptWith(change)}\n else ${flowChats.pending} || ${jsonb([change])} end`;\n\n/** Тот же ключ: прежнее остаётся, но `appliedAt` перехода дописывается к ожидающему без него (как `pendingAdded`). */\nfunction keptWith(change: PendingFlowChange): SQL {\n if (change.kind !== \"transition\" || change.appliedAt === undefined) return sql`${flowChats.pending}`;\n return sql`(select jsonb_agg(case when item->>'key' = ${change.key} and item->'appliedAt' is null and item->>'kind' = 'transition'\n then item || jsonb_build_object('appliedAt', ${change.appliedAt}::text) else item end order by place)\n from jsonb_array_elements(${flowChats.pending}) with ordinality as list(item, place))`;\n}\n\n/** Состояние по карте перевода: удалённые — в выбранные, остальные как были. */\nfunction mappedStateSql(map: StateMap): SQL {\n const entries = Object.entries(map);\n if (entries.length === 0) return sql`${flowChats.stateId}`;\n const cases = entries.map(([from, to]) => sql`when ${from} then ${to}`);\n return sql`case ${flowChats.stateId} ${sql.join(cases, sql` `)} else ${flowChats.stateId} end`;\n}\n\n/** Аренды нет или она кончилась: до `lease_until` включительно она ещё живая. */\nconst leaseFree = (now: Date) => or(isNull(flowChats.leaseUntil), lt(flowChats.leaseUntil, now));\n\n/** Таймер наступил, никто его не держит, разговор идёт (законченный — только ради повтора доставки) и не на паузе. */\nconst dueAndFree = (now: Date) =>\n and(\n lte(flowChats.dueAt, now),\n leaseFree(now),\n or(isNull(flowChats.endedAt), eq(flowChats.dueExitId, FLOW_DELIVERY_EXIT)),\n or(isNull(flowChats.pausedUntil), lte(flowChats.pausedUntil, now)),\n );\n\n/** Запись доставки — та, что ждали: `messageId`, `attempt` и `token` (у записей до 0.81 его нет). */\nfunction deliveryIs(expected: FlowPendingDelivery | null): SQL | undefined {\n if (expected === null) return isNull(flowChats.delivery);\n const clean = withoutNul(expected);\n return and(\n sql`${flowChats.delivery}->>'messageId' = ${clean.messageId}`,\n sql`(${flowChats.delivery}->>'attempt')::integer = ${clean.attempt}`,\n sql`${flowChats.delivery}->>'token' is not distinct from ${clean.token ?? null}::text`,\n );\n}\n\n/** Срок и аренда остаются, если ждёт доставка: у неё свой срок и, может быть, идущая отправка. */\nconst unlessDeliveryWaits = (column: AnyColumn) => sql`case when ${flowChats.delivery} is null then null else ${column} end`;\n\n/** Порядок по кодам символов, как у SQLite и памяти, а не по языку базы. */\nconst inCodeOrder = (column: typeof flowChats.chatId) => sql`${column} collate \"C\"`;\n\n/**\n * Приводит таблицы сценариев к последней миграции. Схема `agent_chat` создаётся под общей блокировкой всех пакетов, миграции — под своей:\n * копии пакета ждут друг друга, а разные пакеты стартуют разом на пустой базе без гонки за схему.\n */\nexport async function migrateFlowsToLatest(pool: Pool, migrationsFolder: string): Promise<void> {\n await migratePostgresSchema({\n connect: () => pool.connect(),\n lockKey: FLOWS_MIGRATION_LOCK_KEY,\n migrate: (client) => migrate(drizzle(client), { migrationsFolder, migrationsSchema: \"agent_chat\", migrationsTable: FLOWS_MIGRATIONS_TABLE }),\n });\n}\n\n/**\n * Сценарии в PostgreSQL (схема `agent_chat`) на пуле приложения. Миграция идёт при первом обращении, каждая операция\n * её дожидается. Копии сервера делят таймеры и перевод чатов через `for update skip locked`: строку, которую уже\n * берёт другая копия, эта пропускает. Закрытие пул не закрывает: он принадлежит приложению.\n */\nexport function createPostgresFlowStore(pool: Pool, migrationsFolder: string): PostgresFlowStore {\n const db = drizzle(pool);\n const ready = migrateFlowsToLatest(pool, migrationsFolder);\n // Ошибка миграции придёт в каждую операцию; без этого она всплыла бы ещё и как необработанная.\n ready.catch(() => {});\n\n async function getChat(chatId: string): Promise<ChatRow | undefined> {\n await ready;\n const [row] = await db.select().from(flowChats).where(eq(flowChats.chatId, chatId));\n return row;\n }\n\n return {\n guarded: true,\n async pinChat(input) {\n await ready;\n const clean = withoutNul(input);\n await db\n .insert(flowChats)\n .values({\n chatId: clean.chatId,\n ownerId: clean.ownerId,\n flowId: clean.flowId,\n versionId: clean.versionId,\n stateId: clean.stateId,\n pending: [],\n timeZone: clean.timeZone,\n ...(clean.placement && { placement: clean.placement }),\n updatedAt: new Date(clean.at),\n })\n .onConflictDoNothing();\n return toRecord((await getChat(clean.chatId))!);\n },\n async getChat(chatId) {\n const row = await getChat(chatId);\n return row ? toRecord(row) : null;\n },\n async updateChat(chatId, patch, at) {\n await ready;\n const clean = withoutNul(patch);\n const [row] = await db\n .update(flowChats)\n .set({ versionId: clean.versionId, stateId: clean.stateId, timeZone: clean.timeZone, placement: clean.placement, updatedAt: new Date(at) })\n .where(eq(flowChats.chatId, chatId))\n .returning();\n return row ? toRecord(row) : null;\n },\n async deleteChat(chatId) {\n await ready;\n await db.delete(flowChats).where(eq(flowChats.chatId, chatId));\n },\n\n async addPending(chatId, change) {\n await ready;\n await db.update(flowChats).set({ pending: pendingWith(withoutNul(change)) }).where(eq(flowChats.chatId, chatId));\n },\n async takePending(chatId) {\n await ready;\n // Старое значение — из строки, захваченной `for update`: вторая копия ждёт её и получает уже пустой список\n // (кроме переходов, применённых в срок: они остаются).\n const result = await db.execute<{ pending: PendingFlowChange[] }>(sql`\n with taken as (select chat_id, pending from ${flowChats} where chat_id = ${chatId} for update)\n update ${flowChats} set pending = (\n select coalesce(jsonb_agg(item order by place), '[]'::jsonb)\n from jsonb_array_elements(taken.pending) with ordinality as list(item, place)\n where item->>'kind' = 'transition' and item->'appliedAt' is not null\n ) from taken\n where ${flowChats.chatId} = taken.chat_id\n returning taken.pending`);\n return result.rows[0]?.pending ?? [];\n },\n\n async setDue(chatId, due, options) {\n await ready;\n await db\n .update(flowChats)\n .set({\n dueAt: due ? new Date(due.at) : null,\n dueExitId: due ? withoutNul(due.exitId) : null,\n dueBranchHead: due ? withoutNul(due.branchHead) : null,\n leaseUntil: null,\n leaseOwner: null,\n })\n .where(\n and(\n eq(flowChats.chatId, chatId),\n options?.unlessDelivery ? isNull(flowChats.delivery) : undefined,\n options?.leaseOwner !== undefined ? eq(flowChats.leaseOwner, withoutNul(options.leaseOwner)) : undefined,\n ),\n );\n },\n async leaseDue({ now, owner, leaseMs, limit }) {\n await ready;\n const nowDate = new Date(now);\n // Строки, которые сейчас берёт другая копия, пропускаются; условие повторено снаружи — его база перепроверит\n // на новой версии строки, если та успела измениться.\n // Выборка — отдельным шагом `with`: подзапрос с пределом внутри `in (…)` база может перечитать и взять больше.\n const due = db.$with(\"due\").as(\n db\n .select({ chatId: flowChats.chatId })\n .from(flowChats)\n .where(dueAndFree(nowDate))\n .orderBy(flowChats.dueAt, inCodeOrder(flowChats.chatId))\n .limit(limit)\n .for(\"update\", { skipLocked: true }),\n );\n const rows = await db\n .with(due)\n .update(flowChats)\n .set({ leaseOwner: withoutNul(owner), leaseUntil: new Date(nowDate.getTime() + leaseMs) })\n .where(and(inArray(flowChats.chatId, db.select({ chatId: due.chatId }).from(due)), dueAndFree(nowDate)))\n .returning();\n return rows.map(toRecord).sort((left, right) => compareText(left.dueAt!, right.dueAt!) || compareText(left.chatId, right.chatId));\n },\n async releaseLease(chatId, owner) {\n await ready;\n await db\n .update(flowChats)\n .set({ leaseOwner: null, leaseUntil: null })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.leaseOwner, owner)));\n },\n async renewLease({ chatId, owner, now, leaseMs }) {\n await ready;\n const renewed = await db\n .update(flowChats)\n .set({ leaseUntil: new Date(new Date(now).getTime() + leaseMs) })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.leaseOwner, withoutNul(owner))))\n .returning({ chatId: flowChats.chatId });\n return renewed.length > 0;\n },\n\n async saveVersion(input) {\n await ready;\n const clean = withoutNul(input);\n const inserted = await db\n .insert(flowVersions)\n .values({ flowId: clean.flowId, versionId: clean.versionId, hash: clean.hash, flow: clean.flow, createdAt: new Date(clean.at) })\n .onConflictDoNothing()\n .returning({ hash: flowVersions.hash });\n if (inserted.length > 0) return \"new\";\n const [saved] = await db\n .select({ hash: flowVersions.hash })\n .from(flowVersions)\n .where(and(eq(flowVersions.flowId, clean.flowId), eq(flowVersions.versionId, clean.versionId)));\n return saved?.hash === clean.hash ? \"same\" : \"conflict\";\n },\n async getVersion(flowId, versionId) {\n await ready;\n const [row] = await db\n .select()\n .from(flowVersions)\n .where(and(eq(flowVersions.flowId, flowId), eq(flowVersions.versionId, versionId)));\n return row ? { ...row, createdAt: toIso(row.createdAt) } : null;\n },\n\n async countChatsByState({ flowId }) {\n await ready;\n const rows = await db\n .select({ versionId: flowChats.versionId, stateId: flowChats.stateId, chats: sql<number>`count(*)::int` })\n .from(flowChats)\n .where(eq(flowChats.flowId, flowId))\n .groupBy(flowChats.versionId, flowChats.stateId);\n return rows.map((row) => ({ ...row, chats: Number(row.chats) })).sort(byVersionAndState);\n },\n async listFlowChats(input) {\n await ready;\n const { flowId, exceptVersionId, chatIds } = withoutNul(input);\n if (chatIds?.length === 0) return [];\n const rows = await db\n .select({\n chatId: flowChats.chatId,\n ownerId: flowChats.ownerId,\n flowId: flowChats.flowId,\n versionId: flowChats.versionId,\n stateId: flowChats.stateId,\n endedAt: flowChats.endedAt,\n endReason: flowChats.endReason,\n })\n .from(flowChats)\n .where(\n and(\n eq(flowChats.flowId, flowId),\n exceptVersionId === undefined ? undefined : ne(flowChats.versionId, exceptVersionId),\n chatIds === undefined ? undefined : inArray(flowChats.chatId, chatIds),\n ),\n )\n .orderBy(inCodeOrder(flowChats.chatId));\n return rows.map((row) => ({ ...row, endedAt: row.endedAt === null ? null : toIso(row.endedAt) }));\n },\n async migrateBatch(input) {\n await ready;\n const clean = withoutNul(input);\n const change = migratePending(clean);\n if (clean.chatIds?.length === 0) return { moved: [] };\n const notYetMoved = and(\n eq(flowChats.flowId, clean.flowId),\n ne(flowChats.versionId, clean.toVersionId),\n clean.chatIds === undefined ? undefined : inArray(flowChats.chatId, clean.chatIds),\n );\n const batch = db\n .select({ chatId: flowChats.chatId })\n .from(flowChats)\n .where(notYetMoved)\n .orderBy(inCodeOrder(flowChats.chatId))\n .limit(clean.limit)\n .for(\"update\", { skipLocked: true });\n const moved = await db\n .update(flowChats)\n .set({ versionId: clean.toVersionId, stateId: mappedStateSql(clean.map), pending: pendingWith(change), updatedAt: new Date(clean.at) })\n .where(and(inArray(flowChats.chatId, batch), notYetMoved))\n .returning({ chatId: flowChats.chatId });\n return { moved: moved.map((row) => row.chatId) };\n },\n\n async readCard(ownerId) {\n await ready;\n // Значение читаем текстом и разбираем сами: иначе строка «007» из jsonb вернулась бы числом 7.\n const rows = await db\n .select({\n fieldId: flowClientCards.fieldId,\n value: sql<string>`${flowClientCards.value}::text`,\n updatedAt: flowClientCards.updatedAt,\n chatId: flowClientCards.chatId,\n flowId: flowClientCards.flowId,\n })\n .from(flowClientCards)\n .where(eq(flowClientCards.ownerId, ownerId));\n return rows\n .map((row) => ({ ...row, value: JSON.parse(row.value) as unknown, updatedAt: toIso(row.updatedAt) }))\n .sort((left, right) => compareText(left.fieldId, right.fieldId));\n },\n async writeCardField(input) {\n await ready;\n const clean = withoutNul(input);\n const entry = { value: jsonb(clean.value), updatedAt: new Date(clean.at), chatId: clean.chatId, flowId: clean.flowId };\n await db\n .insert(flowClientCards)\n .values({ ownerId: clean.ownerId, fieldId: clean.fieldId, ...entry })\n .onConflictDoUpdate({ target: [flowClientCards.ownerId, flowClientCards.fieldId], set: entry });\n },\n async forgetCard(ownerId, fieldId) {\n await ready;\n await db\n .delete(flowClientCards)\n .where(and(eq(flowClientCards.ownerId, ownerId), fieldId === \"all\" ? undefined : eq(flowClientCards.fieldId, fieldId)));\n },\n\n async endChat(chatId, { reason, at }) {\n await ready;\n await db\n .update(flowChats)\n .set({\n endedAt: new Date(at),\n endReason: reason,\n dueAt: unlessDeliveryWaits(flowChats.dueAt),\n dueExitId: unlessDeliveryWaits(flowChats.dueExitId),\n dueBranchHead: unlessDeliveryWaits(flowChats.dueBranchHead),\n leaseUntil: unlessDeliveryWaits(flowChats.leaseUntil),\n leaseOwner: unlessDeliveryWaits(flowChats.leaseOwner),\n })\n .where(and(eq(flowChats.chatId, chatId), isNull(flowChats.endedAt)));\n const row = await getChat(chatId);\n return row ? toRecord(row) : null;\n },\n async reopenChat(chatId, { endedAt, at }) {\n await ready;\n const [row] = await db\n .update(flowChats)\n .set({ endedAt: null, endReason: null, reopenedAt: new Date(at) })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.endedAt, new Date(endedAt))))\n .returning();\n return row ? toRecord(row) : null;\n },\n async setPause(chatId, until) {\n await ready;\n const [row] = await db\n .update(flowChats)\n .set({ pausedUntil: until === null ? null : new Date(until) })\n .where(eq(flowChats.chatId, chatId))\n .returning();\n return row ? toRecord(row) : null;\n },\n async setWindowClosed(chatId, closed) {\n await ready;\n await db.update(flowChats).set({ windowClosed: closed }).where(eq(flowChats.chatId, chatId));\n },\n async setDayClosed(chatId, day) {\n await ready;\n await db.update(flowChats).set({ dayClosedOn: day === null ? null : withoutNul(day) }).where(eq(flowChats.chatId, chatId));\n },\n async setDelivery(chatId, delivery) {\n await ready;\n await db\n .update(flowChats)\n .set({ delivery: delivery && withoutNul(normalizedDelivery(delivery)) })\n .where(eq(flowChats.chatId, chatId));\n },\n async swapDelivery(chatId, { expected, next, due, claim, freeAt, endedAt }) {\n await ready;\n const claimAt = claim && new Date(claim.now);\n const swapped = await db\n .update(flowChats)\n .set({\n delivery: next && withoutNul(normalizedDelivery(next)),\n ...(due !== undefined && {\n dueAt: due ? new Date(due.at) : null,\n dueExitId: due ? withoutNul(due.exitId) : null,\n dueBranchHead: due ? withoutNul(due.branchHead) : null,\n }),\n ...(claim && { leaseOwner: withoutNul(claim.owner), leaseUntil: new Date(claimAt!.getTime() + claim.leaseMs) }),\n })\n .where(\n and(\n eq(flowChats.chatId, chatId),\n deliveryIs(expected),\n claim\n ? or(leaseFree(claimAt!), eq(flowChats.leaseOwner, withoutNul(claim.owner)), claim.from !== undefined ? eq(flowChats.leaseOwner, withoutNul(claim.from)) : undefined)\n : undefined,\n freeAt !== undefined ? leaseFree(new Date(freeAt)) : undefined,\n endedAt === undefined ? undefined : endedAt === null ? isNull(flowChats.endedAt) : eq(flowChats.endedAt, new Date(endedAt)),\n ),\n )\n .returning({ chatId: flowChats.chatId });\n return swapped.length > 0;\n },\n\n async readTimeZone(ownerId) {\n await ready;\n const [row] = await db.select({ timeZone: flowPeople.timeZone }).from(flowPeople).where(eq(flowPeople.ownerId, ownerId));\n return row?.timeZone ?? null;\n },\n async writeTimeZone(ownerId, timeZone, at) {\n await ready;\n const clean = withoutNul({ ownerId, timeZone });\n await db\n .insert(flowPeople)\n .values({ ...clean, updatedAt: new Date(at) })\n .onConflictDoUpdate({ target: flowPeople.ownerId, set: { timeZone: clean.timeZone, updatedAt: new Date(at) } });\n },\n\n async forgetSideTasks(ownerId) {\n await ready;\n await db.delete(flowSideTasks).where(eq(flowSideTasks.ownerId, ownerId));\n },\n async readSideTasks(ownerId, chatId) {\n await ready;\n const rows = await db\n .select()\n .from(flowSideTasks)\n .where(and(eq(flowSideTasks.ownerId, ownerId), or(eq(flowSideTasks.chatId, \"\"), eq(flowSideTasks.chatId, chatId))));\n return rows.map(toSideTask).sort((left, right) => compareText(left.taskId, right.taskId));\n },\n async noteSideTask(input) {\n await ready;\n const clean = withoutNul(input);\n const asked = clean.status === \"asked\";\n const doneAt = asked ? null : new Date(clean.at);\n const [row] = await db\n .insert(flowSideTasks)\n .values({ ownerId: clean.ownerId, taskId: clean.taskId, chatId: clean.chatId ?? \"\", asks: asked ? 1 : 0, doneAt })\n .onConflictDoUpdate({\n target: [flowSideTasks.ownerId, flowSideTasks.taskId, flowSideTasks.chatId],\n set: asked ? { asks: sql`${flowSideTasks.asks} + 1` } : { doneAt: sql`coalesce(${flowSideTasks.doneAt}, excluded.done_at)` },\n })\n .returning();\n return toSideTask(row!);\n },\n\n async countFirstMessages(ownerId, day) {\n await ready;\n const [row] = await db\n .select({ count: flowFirstMessages.count })\n .from(flowFirstMessages)\n .where(and(eq(flowFirstMessages.ownerId, ownerId), eq(flowFirstMessages.day, day)));\n return row?.count ?? 0;\n },\n async addFirstMessage(ownerId, day) {\n await ready;\n const [row] = await db\n .insert(flowFirstMessages)\n .values({ ownerId: withoutNul(ownerId), day, count: 1 })\n .onConflictDoUpdate({ target: [flowFirstMessages.ownerId, flowFirstMessages.day], set: { count: sql`${flowFirstMessages.count} + 1` } })\n .returning({ count: flowFirstMessages.count });\n return row!.count;\n },\n\n async close() {\n // Дожидаемся миграции: пул приложения можно закрывать сразу после.\n await ready.catch(() => {});\n },\n };\n}\n","import type { AgentFlow } from \"../format/flow-format\";\nimport type { FlowEndReason } from \"../engine/flow-run\";\nimport type { FlowChatPlacement, FlowPendingDelivery, PendingFlowChange } from \"./flow-store\";\nimport { 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 });\n\n/** Чаты, которые идут по сценарию. */\nexport const flowChats = agentChatSchema.table(\n \"flow_chats\",\n {\n chatId: text(\"chat_id\").primaryKey(),\n ownerId: text(\"owner_id\").notNull(),\n flowId: text(\"flow_id\").notNull(),\n versionId: text(\"version_id\").notNull(),\n stateId: text(\"state_id\").notNull(),\n pending: jsonb(\"pending\").$type<PendingFlowChange[]>().notNull().default([]),\n dueAt: moment(\"due_at\"),\n dueExitId: text(\"due_exit_id\"),\n dueBranchHead: text(\"due_branch_head\"),\n timeZone: text(\"time_zone\").notNull(),\n leaseUntil: moment(\"lease_until\"),\n leaseOwner: text(\"lease_owner\"),\n updatedAt: moment(\"updated_at\").notNull(),\n endedAt: moment(\"ended_at\"),\n endReason: text(\"end_reason\").$type<FlowEndReason>(),\n reopenedAt: moment(\"reopened_at\"),\n pausedUntil: moment(\"paused_until\"),\n windowClosed: boolean(\"window_closed\").notNull().default(false),\n dayClosedOn: text(\"day_closed_on\"),\n delivery: jsonb(\"delivery\").$type<FlowPendingDelivery>(),\n placement: jsonb(\"placement\").$type<FlowChatPlacement>(),\n },\n (table) => [\n index(\"flow_chats_due_idx\").on(table.dueAt),\n index(\"flow_chats_flow_state_idx\").on(table.flowId, table.versionId, table.stateId),\n index(\"flow_chats_owner_idx\").on(table.ownerId),\n ],\n);\n\n/** Сохранённые версии сценариев: чат идёт по своей версии, пока его не перевели. */\nexport const flowVersions = agentChatSchema.table(\n \"flow_versions\",\n {\n flowId: text(\"flow_id\").notNull(),\n versionId: text(\"version_id\").notNull(),\n hash: text(\"hash\").notNull(),\n flow: jsonb(\"flow\").$type<AgentFlow>().notNull(),\n createdAt: moment(\"created_at\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.flowId, table.versionId] })],\n);\n\n/** Карточка клиента: поле за полем, переживает чаты. */\nexport const flowClientCards = agentChatSchema.table(\n \"flow_client_cards\",\n {\n ownerId: text(\"owner_id\").notNull(),\n fieldId: text(\"field_id\").notNull(),\n value: jsonb(\"value\").notNull(),\n updatedAt: moment(\"updated_at\").notNull(),\n chatId: text(\"chat_id\").notNull(),\n flowId: text(\"flow_id\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.fieldId] })],\n);\n\n/** Сколько сообщений агент написал первым за сутки человека. */\nexport const flowFirstMessages = agentChatSchema.table(\n \"flow_first_messages\",\n {\n ownerId: text(\"owner_id\").notNull(),\n day: text(\"day\").notNull(),\n count: integer(\"count\").notNull().default(0),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.day] })],\n);\n\n/** Пояс человека: общий для всех его чатов. */\nexport const flowPeople = agentChatSchema.table(\"flow_people\", {\n ownerId: text(\"owner_id\").primaryKey(),\n timeZone: text(\"time_zone\").notNull(),\n updatedAt: moment(\"updated_at\").notNull(),\n});\n\n/** Попытки попутных задач. `chat_id` пустой — попытки на человека, иначе — на разговор. */\nexport const flowSideTasks = agentChatSchema.table(\n \"flow_side_tasks\",\n {\n ownerId: text(\"owner_id\").notNull(),\n taskId: text(\"task_id\").notNull(),\n chatId: text(\"chat_id\").notNull().default(\"\"),\n asks: integer(\"asks\").notNull().default(0),\n doneAt: moment(\"done_at\"),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.taskId, table.chatId] })],\n);\n"],"mappings":"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","names":["jsonb"]}
package/dist/react.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  FLOW_DEADLINE_NOTICE,
4
4
  FLOW_TIMER_NOTICE,
5
5
  fieldValueText
6
- } from "./chunk-CZ3EDVYW.js";
6
+ } from "./chunk-7ES2PXNH.js";
7
7
  import {
8
8
  ANY_STATE_LAYOUT_KEY,
9
9
  DEFAULT_LATE_START_HOURS,
package/dist/sqlite.js CHANGED
@@ -2,12 +2,14 @@ import {
2
2
  FLOWS_MIGRATIONS_TABLE
3
3
  } from "./chunk-BNMQUNSI.js";
4
4
  import {
5
+ FLOW_DELIVERY_EXIT,
5
6
  appliedInTime,
6
7
  byVersionAndState,
7
8
  compareText,
8
9
  migratePending,
10
+ normalizedDelivery,
9
11
  pendingAdded
10
- } from "./chunk-DCTRMCGV.js";
12
+ } from "./chunk-DOKHNTPU.js";
11
13
 
12
14
  // src/sqlite.ts
13
15
  import { fileURLToPath } from "url";
@@ -140,12 +142,23 @@ function mappedStateSql(map) {
140
142
  const cases = entries.map(([from, to]) => sql`when ${from} then ${to}`);
141
143
  return sql`case ${flowChats.stateId} ${sql.join(cases, sql` `)} else ${flowChats.stateId} end`;
142
144
  }
145
+ var leaseFree = (now) => or(isNull(flowChats.leaseUntil), lt(flowChats.leaseUntil, now));
143
146
  var dueAndFree = (now) => and(
144
147
  lte(flowChats.dueAt, now),
145
- or(isNull(flowChats.leaseUntil), lt(flowChats.leaseUntil, now)),
146
- isNull(flowChats.endedAt),
148
+ leaseFree(now),
149
+ or(isNull(flowChats.endedAt), eq(flowChats.dueExitId, FLOW_DELIVERY_EXIT)),
147
150
  or(isNull(flowChats.pausedUntil), lte(flowChats.pausedUntil, now))
148
151
  );
152
+ function deliveryIs(expected) {
153
+ if (expected === null) return isNull(flowChats.delivery);
154
+ const field = (name) => sql`json_extract(${flowChats.delivery}, ${`$.${name}`})`;
155
+ return and(
156
+ sql`${field("messageId")} = ${expected.messageId}`,
157
+ sql`${field("attempt")} = ${expected.attempt}`,
158
+ expected.token === void 0 ? sql`${field("token")} is null` : sql`${field("token")} = ${expected.token}`
159
+ );
160
+ }
161
+ var unlessDeliveryWaits = (column) => sql`case when ${flowChats.delivery} is null then null else ${column} end`;
149
162
  function createSqliteFlowStore(filePath, migrationsFolder) {
150
163
  const sqlite = new Database(filePath);
151
164
  sqlite.pragma("journal_mode = WAL");
@@ -154,6 +167,7 @@ function createSqliteFlowStore(filePath, migrationsFolder) {
154
167
  migrate(db, { migrationsFolder, migrationsTable: FLOWS_MIGRATIONS_TABLE });
155
168
  const getChat = (chatId) => db.select().from(flowChats).where(eq(flowChats.chatId, chatId)).get();
156
169
  return {
170
+ guarded: true,
157
171
  async pinChat(input) {
158
172
  db.insert(flowChats).values({
159
173
  chatId: input.chatId,
@@ -203,7 +217,13 @@ function createSqliteFlowStore(filePath, migrationsFolder) {
203
217
  dueBranchHead: due?.branchHead ?? null,
204
218
  leaseUntil: null,
205
219
  leaseOwner: null
206
- }).where(and(eq(flowChats.chatId, chatId), options?.unlessDelivery ? isNull(flowChats.delivery) : void 0)).run();
220
+ }).where(
221
+ and(
222
+ eq(flowChats.chatId, chatId),
223
+ options?.unlessDelivery ? isNull(flowChats.delivery) : void 0,
224
+ options?.leaseOwner !== void 0 ? eq(flowChats.leaseOwner, options.leaseOwner) : void 0
225
+ )
226
+ ).run();
207
227
  },
208
228
  async leaseDue({ now, owner, leaseMs, limit }) {
209
229
  const nowMs = toMs(now);
@@ -275,7 +295,15 @@ function createSqliteFlowStore(filePath, migrationsFolder) {
275
295
  db.delete(flowClientCards).where(and(eq(flowClientCards.ownerId, ownerId), fieldId === "all" ? void 0 : eq(flowClientCards.fieldId, fieldId))).run();
276
296
  },
277
297
  async endChat(chatId, { reason, at }) {
278
- db.update(flowChats).set({ endedAt: toMs(at), endReason: reason, dueAt: null, dueExitId: null, dueBranchHead: null, leaseUntil: null, leaseOwner: null }).where(and(eq(flowChats.chatId, chatId), isNull(flowChats.endedAt))).run();
298
+ db.update(flowChats).set({
299
+ endedAt: toMs(at),
300
+ endReason: reason,
301
+ dueAt: unlessDeliveryWaits(flowChats.dueAt),
302
+ dueExitId: unlessDeliveryWaits(flowChats.dueExitId),
303
+ dueBranchHead: unlessDeliveryWaits(flowChats.dueBranchHead),
304
+ leaseUntil: unlessDeliveryWaits(flowChats.leaseUntil),
305
+ leaseOwner: unlessDeliveryWaits(flowChats.leaseOwner)
306
+ }).where(and(eq(flowChats.chatId, chatId), isNull(flowChats.endedAt))).run();
279
307
  const row = getChat(chatId);
280
308
  return row ? toRecord(row) : null;
281
309
  },
@@ -294,8 +322,24 @@ function createSqliteFlowStore(filePath, migrationsFolder) {
294
322
  db.update(flowChats).set({ dayClosedOn: day }).where(eq(flowChats.chatId, chatId)).run();
295
323
  },
296
324
  async setDelivery(chatId, delivery) {
297
- const clean = delivery === null ? null : { ...delivery, retryAt: toIso(toMs(delivery.retryAt)) };
298
- db.update(flowChats).set({ delivery: clean }).where(eq(flowChats.chatId, chatId)).run();
325
+ db.update(flowChats).set({ delivery: delivery && normalizedDelivery(delivery) }).where(eq(flowChats.chatId, chatId)).run();
326
+ },
327
+ async swapDelivery(chatId, { expected, next, due, claim, freeAt, endedAt }) {
328
+ const claimAt = claim && toMs(claim.now);
329
+ const swapped = db.update(flowChats).set({
330
+ delivery: next && normalizedDelivery(next),
331
+ ...due !== void 0 && { dueAt: due ? toMs(due.at) : null, dueExitId: due?.exitId ?? null, dueBranchHead: due?.branchHead ?? null },
332
+ ...claim && { leaseOwner: claim.owner, leaseUntil: claimAt + claim.leaseMs }
333
+ }).where(
334
+ and(
335
+ eq(flowChats.chatId, chatId),
336
+ deliveryIs(expected),
337
+ claim ? or(leaseFree(claimAt), eq(flowChats.leaseOwner, claim.owner), claim.from !== void 0 ? eq(flowChats.leaseOwner, claim.from) : void 0) : void 0,
338
+ freeAt !== void 0 ? leaseFree(toMs(freeAt)) : void 0,
339
+ endedAt === void 0 ? void 0 : endedAt === null ? isNull(flowChats.endedAt) : eq(flowChats.endedAt, toMs(endedAt))
340
+ )
341
+ ).returning({ chatId: flowChats.chatId }).all();
342
+ return swapped.length > 0;
299
343
  },
300
344
  async readTimeZone(ownerId) {
301
345
  return db.select({ timeZone: flowPeople.timeZone }).from(flowPeople).where(eq(flowPeople.ownerId, ownerId)).get()?.timeZone ?? null;